本节书摘来自异步社区《深入理解Scala》一书中的第1章,第1.2节Scala的当函数式编程遇见面向对象,作者[美]Josh Suereth,更多章节内容可以访问云栖社区“异步社区”公众号查看

1.2 当函数式编程遇见面向对象
深入理解Scala
函数式编程和面向对象编程是软件开发的两种不同途径。函数式编程并非什么新概念,在现代开发者的开发工具箱里也绝非是什么天外来客。我们将通过Java生态圈里的例子来展示这一点,主要来看Spring Application framework和Google Collections库。这两个库都在Java的面向对象基础上融合了函数式的概念,而如果我们把它们翻译成Scala,则会优雅得多。在深入之前,我们需要先理解面向对象编程和函数式编程这两个术语的含义。
面向对象编程是一种自顶向下的程序设计方法。用面向对象方法构造软件时,我们将代码以名词(对象)做切割,每个对象有某种形式的标识符(self/this)、行为(方法)、和状态(成员变量)。识别出名词并且定义出它们的行为后,再定义出名词之间的交互。实现交互时存在一个问题,就是这些交互必须放在其中一个对象中(而不能独立存在)。现代面向对象设计倾向于定义出“服务类”,将操作多个领域对象的方法集合放在里面。这些服务类,虽然也是对象,但通常不具有独立状态,也没有与它们所操作的对象无关的独立行为。
函数式编程方法通过组合和应用函数来构造软件。函数式编程倾向于将软件分解为其需要执行的行为或操作,而且通常采用自底向上的方法。函数式编程中的函数概念具有一定的数学上的含义,纯粹是对输入进行操作,产生结果。所有变量都被认为是不可变的。函数式编程中对不变性的强调有助于编写并发程序。函数式编程试图将副作用推迟到尽可能晚。从某种意义上说,消除副作用使得对程序进行推理(reasoning)变得较为容易。函数式编程还提供了非常强大的对事物进行抽象和组合的能力。
表1.1 面向对象和函数式编程的一般特点


  • 模式匹配
    函数式编程和面向对象编程从不同的视角看待软件。这种视角上的差异使得它们非常互补。面向对象可以处理名词而函数式编程能够处理动词。其实近年来很多Java程序员已经开始转向这一策略(分离名词和动词)。EJB规范将软件切分为用来容纳行为的Session bean和用来为系统中的名词建模的Entity bean。无状态Session bean看上去就更像是函数式代码的集合了(尽管欠缺了很多函数式代码有用的特性)。

这种朝函数式风格方向的推动远不止EJB规范。Spring框架的模板类(Template classes)就是一种非常函数式的风格,而Google Collections库在设计上就非常的函数式。我们先来看一下这些通用的Java库,然后看看Scala的函数式和混合面向对象编程能怎样增强这些API。

1.2.1 重新发现函数式概念
很多现代API设计时都融入了函数式编程的好东西而又不称自己是函数式编程。对于Java来说,像Google Collections和Spring应用框架以Java库的形式使Java程序员也能接触到流行的函数式编程概念。Scala更进一步,将函数式编程直接融合到了语言里。我们来将流行的Spring框架中的JdbcTemplate类简单地翻译成Scala,看看它在Scala下会是什么样子。


现在,来直译一下,我们把接口转换为有相同方法的特质(trait)。


简单的直译也很有意思,不过它还是非常的Java。我们现在来深挖一下,特别看看PreparedStatementCreator和RowMapper接口。


PreparedStatementCreator接口只有一个方法。这个方法接受JDBC连接,返回PreparedStatement. RowMapper接口看上去也差不多。


Scala提供了一等函数(first-class function),利用这个特性,我们可以把JdbcTemplate查询方法改成接受函数而不是接口作为参数。这些函数应该跟接口里的基础方法有相同的签名。本例中,PreparedStatementCreator参数可以替换为一个函数,这个函数接受Connection,返回PreparedStatement. RowMapper可以替换成一个接受ResultSet和整数,返回某种对象类型的函数。更新后的Scala版本JdbcTemplate如下。


现在query方法变得更函数式了。它使用了称为租借模式(loaner pattern)的技巧。这种技巧的大意在于让一些主控的实体(controlling entity)—本例中是JdbcTemplate—由它来构造资源,然后将资源的使用委托给另一个函数。本例中有两个函数和三种资源。同时,其名字JdbcTemplate隐含的意思是它是个模板方法,其部分行为是有待用户去实现的。在纯面向对象编程中,这一点通常通过继承来做到。在较为函数式的方法中,这些行为碎片(behavioral pieces)成为了传给主控函数的参数。这样就能通过混合/匹配参数提供更多的灵活性,而无需不断地使用子类继承。
你可能会奇怪为什么我们用AnyRef作为第二个参数的返回值。Scala中的AnyRef就相当于Java里的java.lang.Object。既然Scala支持泛型,即使要编译成jvm1.4字节码,我们也应该进一步修改接口移除AnyRef,允许用户返回特定类型。


仅稍做转换,我们就创建了一个直接使用函数参数的接口。这比之前略为函数式一点,仅仅是因为Scala的函数特质允许组合。
当你读完本书的时候,你将能做出与此接口完全不同的设计。不过我们现在还是继续查看Java生态圈里的函数式设计。尤其是Google Collections API。

1.2.2 Google Collections中的函数式概念
Google Collections API给标准Java集合库增加了很多功能,主要包括一组高效的不可变数据结构和一些操作集合的函数式方法,主要是Function接口和Predicate(谓词)接口。这些接口主要用在通过Iterables和Iterators类上。我们来看下Predicate接口的使用方法。


Predicate接口非常简单。除了equals方法,它就只有一个“apply”方法。apply方法接受参数,返回true或false。Iterators/Iterables的“filter”方法用到了这个接口。filter方法接受一个集合和一个谓词作为参数,返回一个新集合,仅包含被predicate的apply方法判定为true的元素。在find方法里也用到了Predicate接口。find方法在集合中查找并返回第一个满足predicate的元素。下面列出filter和find方法签名。


另外还有个Predicates类,里面有一些用于组合断言(与和或等)的静态方法,还有一些常用的标准谓词,如“not null”等。这个简单的接口让我们可以用很简洁的代码通过组合的方式实现强大的功能。同时,因为predicate本身被传入到filter函数里面(而不是把集合传入到predicate里),filter函数可以自行决定执行断言的最佳方法或时机。比如(filter背后的集合)数据结构有可能决定采用延迟计算(lazily evaluating)断言的策略,那它可以返回原集合的一个视图(view)。它也可能决定在创建新集合的时候采用某种并行策略。 关键是这些都被抽象掉了,使得库可以随时改进而不影响用户的代码。
Predicate接口自身也很有趣,因为它看上去就像个简单的函数。这个函数接受某个类型T,返回一个布尔值,在Scala里用T => Boolean表示。我们用Scala来重写一下filter/find方法,看看它们的函数签名怎样定义。


你会立刻注意到Scala里无需显示的标注“?super T”,因为Scala的Function接口已经恰当地标注了协变(Covariance)和逆变(Contravariance)。如果某类型可以强制转换为子孙类,我们称为协变(+T或? extends T),如果某类型可以强制转换为祖先类,我们称为逆变(-T或? super T)。如果某类型完全不能被强制转换,就称为不变(Invariance)。在这个例子里,断言的参数可以在需要的时候强制转换为其祖先类型。举例来说,如果猫是哺乳动物的子类,那么一个针对哺乳动物的断言也能用于猫的集合。在Scala中,你可以在类定义的时候指定其为协变/逆变/不变。
那么在Scala里怎么组合断言呢?我们可以利用函数式组合的特性非常方便地实现一些组合功能。我们来用Scala实现一个新的Predicates模块,这个模块接受(多个)函数断言作为参数,提供它们的常用组合函数。这些组合函数的输入类型应该是T => Boolean,输入类型也是T => Boolean。初始的(组合前的)断言应该也是T => Boolean类型。


现在我们开始踏入函数式编程的领域了。我们定义了一等函数(first-class function),然后把它们组合起来提供新的功能。你应该注意到了or方法接受两个断言,f1和f2,然后产生一个匿名函数,这个函数接受参数t,然后把f1(t)和f2(t)的结果“or”一下。函数式编程也更充分地利用了泛型和类型系统的能力。Scala投入了很多心血来减少使用泛型时的困难,使泛型可以被“日常使用”。
函数式编程并不仅仅就是把函数组合起来而已。函数式编程的精髓在于尽可能地推迟副作用。上例中的Predicate对象定义了一个简单的组合机制,只是用来组合谓词(而不执行)。直到实际的谓词传递给Iterables对象后才产生副作用。这个区分很重要。我们可以用Preicate对象提供的辅助方法把简单的谓词组合成很复杂的谓词。
函数式编程给我们提供了手段来推迟程序中改变状态的部分。它提供了机制让我们构造“动词”,同时又推迟副作用。这些动词可以用更方便推理(reasoning)的方式组合起来.直到最后,这些“动词”才被应用到系统中的“名词”上。传统的函数式编程风格是要求把副作用推到越晚越好。混合式面向对象-函数式编程(OO-FP),则是一种混合式风格(the idioms merge)
接下来我们看看Scala怎么解决类型系统和富有表达力的代码之间的矛盾。

《深入理解Scala》——第1章,第1.2节当函数式编程遇见面向对象相关推荐

  1. Scala中的函数式编程与面向对象编程知识点复习整理(二)——面向对象编程

    面向对象基础 概述 Scala是一门完全面向对象的语言,摒弃了Java中很多不是面向对象的语法,虽然如此,但其面向对象思想和Java的面向对象思想还是一致的. package 在java中     作 ...

  2. Scala 中将方法、函数、函数式编程和面向对象编程关系分析图

  3. 翻译连载 | JavaScript轻量级函数式编程-第4章:组合函数 |《你不知道的JS》姊妹篇...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  4. 翻译连载 | JavaScript轻量级函数式编程-第7章: 闭包vs对象 |《你不知道的JS》姊妹篇...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  5. 翻译连载 | JavaScript轻量级函数式编程-第5章:减少副作用 |《你不知道的JS》姊妹篇...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  6. 翻译连载 | JavaScript轻量级函数式编程-第 8 章:列表操作 |《你不知道的JS》姊妹篇

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  7. 翻译连载 | JavaScript轻量级函数式编程-第 8 章:列表操作 |《你不知道的JS》姊妹篇...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  8. Scala入门系列(7)-Scala之函数式编程

    基础 概念 函数式编程 函数式编程中的函数指的并不是编程语言中的函数(或方法),它指的是数学意义上的函数,即映射关系(如:y = f(x)),就是 y 和 x 的对应关系. 数学上对于函数的定义是这样 ...

  9. 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 4 章:组合函数...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

最新文章

  1. Bootstrap中模态框多层嵌套时滚动条问题
  2. 【C语言】运算符优先级(仅供自学)
  3. Java基础学习总结(88)——线程创建与终止、互斥、通信、本地变量
  4. 君信财富获数千万元战略投资,投资方为银江集团
  5. gstream初次尝试
  6. java安装后在哪里打开_java安装后怎么打开教程
  7. html表格的行合并代码,HTML代码制作的表格合并单元格教程
  8. excel组合汇总_Excel汇总20151102
  9. 移植linux内核串口配置,uClinux内核的移植 - bootloader对uClinux的S3C44B0移植
  10. Windows免杀木马+维持权限(shellter)
  11. Xmind 8 pro 软件破解版
  12. 开源学校管理系统php,SchoolCMS学校管理系统 v2.3
  13. 建筑物防雷接地工程的分类和措施
  14. [渗透教程]-006-渗透测试-Metasploit以及实战教程
  15. python面试题总结
  16. openfoam CourantNo.H
  17. 早期股权分配不是有钱就能搞定的!
  18. shell: mysql删除183天前的table(保留半年的log数据)
  19. PPM、PCM和PWM的区别, I2S与pcm的区别
  20. 逆向分析ObRegisterCallbacks学习回调结构

热门文章

  1. 中国央行数字货币DCEP——有增值空间吗?
  2. 2018最新BIM设计的秘密武器——揭秘全球顶级工程设计师的杀手锏
  3. CrossOver2023mac切换win双系统虚拟机
  4. 单链表操作10-带头结点的单链表逆置(个人学习笔记,仅供参考)
  5. whea_uncorrectable_error怎么解决?
  6. 零钱通案例---面向过程实现
  7. 微信小程序项目源码ssm校园二手交易小程序+后台管理系统|前后分离VUE含论文+PPT+源码
  8. maya2014启动失败的解决
  9. 虾皮跨境电商,2022年还适合做吗?知虾数据告诉你答案
  10. CLocation-国行安卓手机使用Google定位服务解决方案