函数式编程是最近被热炒的一个概念。国内外众多大牛纷纷发表文章,认为函数编程可能会再度兴起。搞得一贯喜欢跟风的小弟我如坐针毡。所以,也抽空研究了一下函数式编程这个时髦的概念。
上个世纪,我曾经在图书馆借了一本介绍全部主要计算机语言的书,那本书简单得介绍过Lisp和其余语言的语法。其中提到,Lisp是一门函数语言。固然,那时对这句话没什么概念。
命令式编程是一种用程序状态描述计算的方法。使用这种范型的编程人员用语句改变程序状态。这就是为何,像 Java 这样的程序是由一系列让计算机执行的命令 (或者语句) 所组成的。
另外一方面,函数式编程是一种强调表达式的计算而非命令的执行的一种编程风格。表达式是用函数结合基本值构成的,它相似于用参数调用函数。
也就是说,函数式编程主要是函数调用,而不是其它的程序语句。
而命令式编程,是经过程序语句的执行运行的。程序语句的执行,会改变程序中保存的状态。
实际上,咱们通常使用的命令式语言,如C++,Java,C#等的代码中,也能够看到大量的函数调用。
一个优秀的软件工程师使用面向对象编程语言编写出来的代码,除了少数的建立对象实例的代码外,大量的代码都是函数调用。
所以,尽管传统上认为C++,Java,C#等面向对象编程语言是命令式编程语言。但咱们同样能够在面向对象编程语言中实现函数式编程风格!
实际上,可能你写的很多代码也是采用了函数式编程风格,只是你不知道罢了!
什么是函数编程?
在常常被引用的论文 “Why Functional Programming Matters”(请参阅 参考资料) 中,做者 John Hughes 说明了模块化是成功编程的关键,而函数编程能够极大地改进模块化。在函数编程中,编程人员有一个自然框架用来开发更小的、更简单的和更通常化的模块, 而后将它们组合在一块儿。函数编程的一些基本特色包括:
支持闭包和高阶函数。
支持懒惰计算(lazy evaluation)。
使用递归做为控制流程的机制。
增强了引用透明性。
没有反作用。
闭包和高阶函数和命令模式
闭包和高阶函数
函数编程支持函数做为第一类对象,有时称为 闭包或者 仿函数(functor)对象。实质上,闭包是起函数的做用并能够像对象同样操做的对象。与此相似,FP 语言支持 高阶函数。高阶函数能够用另外一个函数(间接地,用一个表达式) 做为其输入参数,在某些状况下,它甚至返回一个函数做为其输出参数。这两种结构结合在一块儿使得能够用优雅的方式进行模块化编程,这是使用 FP 的最大好处。
看到这里,我想有设计模式经验的朋友必定会联想到“命令模式”。
是的!面向对象编程中的命令模式,不就是函数式编程中的闭包和告诫函数吗?!
命令模式
别名:Action动做模式,Transaction事务模式。我也叫它“参数回调模式”,由于本质上,命令模式和C的参数回调是同样的。
意图
|
将一个请求封装为一个对象,从而使你可用不一样的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操做。
|
适用性
|
- 抽象出待执行的动做以参数化某对象,你可用过程语言中的回调(c a l l b a c k )函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个须要的时候被调用。C o m m a n d 模式是回调机制的一个面向对象的替代品。
- 在不一样的时刻指定、排列和执行请求。一个C o m m a n d 对象能够有一个与初始请求无关的生存期。若是一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另外一个不一样的进程并在那儿实现该请求。
- 支持取消操做。C o m m a n d 的E x c u t e 操做可在实施操做前将状态存储起来,在取消操做时这个状态用来消除该操做的影响。C o m m a n d 接口必须添加一个U n e x e c u t e 操做,该操做取消上一次E x e c u t e 调用的效果。执行的命令被存储在一个历史列表中。可经过向后和向前遍历这一列表并分别调用U n e x e c u t e 和E x e c u t e 来实现重数不限的“取消”和“重作”。
(这是 指,备忘录模式中,撤销管理器经过方法的参数,把状态加到管理器中呢?!同时,容器中的全部状态都是一个接口的实现类,而后,均可以执行相同的方法:
undo/redo。
)
- 支持修改日志,这样当系统崩溃时,这些修改能够被重作一遍。在C o m m a n d 接口中添加装载操做和存储操做,能够用来保持变更的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中从新读入记录下来的命令并用E x e c u t e 操做从新执行它们。
- 用构建在原语操做上的高层操做构造一个系统。这样一种结构在支持事务( t r a n s a c t i o n )的信息系统中很常见。一个事务封装了对数据的一组变更。C o m m a n d 模式提供了对事务进行建模的方法。C o m m a n d 有一个公共的接口,使得你能够用同一种方式调用全部的事务。同时使用该模式也易于添加新事务以扩展系统。
|
C++中STL和Boost等类库中普遍使用的仿函数类,也是命令模式的一种实现。
在面向过程编程语言,或者函数编程语言中,经过把函数指针做为函数的参数,能够实现参数回调。
在面向对象编程语言中,经过把某个仿函数接口的指针做为函数的参数,也能够实现相似于面向过程语言的函数参数的回调。
面向对象编程语言实现命令模式有几种变体。
如,能够把某个仿函数接口的指针做为类的一个实例变量保存在类中。
闭包和高阶函数和命令模式
函数式编程中的闭包,对应于命令模式中,用于回调的接口。这个接口封装了一个或者多个函数。
如:public interface Comparable<T>
有一个方法
Comparable接口就是一个闭包。它的具体实现类就是闭包的具体实现。
Comparable接口仅仅封装了一个方法。它经常用做方法的参数,方法体内进行调用参数的
compareTo
方法。
使用了回调参数的那个函数,就是函数式编程中的“高阶函数”。
为命令模式正名
一直以来,在面向对象编程语言的世界中,对GOF提出的命令模式的非议一直不断。那些纯粹的面向对象编程专家看到命令模式中那些个只是封装了一个函数的接口感到恐惧。
那是函数,仍是类?这仍是OOP吗?
面向对象编程,使用接口描述世界。纯粹的OOP语言,如Java,Ruby和C#中,只有类是第一类的语言元素。函数都是封装在一个个类中的。
可是,类只是咱们对于世界的一种描述,一种观点。对于世界中那些纯粹的功能,怎么办?难道非要给它们加上它们并不须要的数据吗?
仍是还函数以原本面目吧!使用命令模式,用一个接口把函数封装起来!实话实说:咱们就是须要一个函数!怎么样?不行吗!
据支持函数式编程的大牛们说,函数式编程比命令式编程更增强大。并且这是有数学依据的。究竟是不是真的,我不知道。我对理论没什么兴趣。
至少,一向支持命令模式的我,为命令模式找到了强援。若是面向对象社区不要命令模式,那么咱们就索性声称命令模式就是函数式编程得了!
命令模式是面向对象编程语言中模仿函数式编程的一种模式!
下面再说说命令模式其余的实现方式
除了使用一个仿函数接口(或者说函数的包装接口)外,对于某些语言,命令模式还有其余的实现方式。
Java和C#都有很强大的动态能力。它们的反射机制能够动态获得类、函数、属性。
在Java中,
Method类就是对全部函数的封装类。
public final class Method
extends AccessibleObject
implements GenericDeclaration, Member
Method
提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法多是类方法或实例方法(包括抽象方法)。
Method
容许在匹配要调用的实参与底层方法的形参时进行扩辗转换;但若是要进行收缩转换,则会抛出
IllegalArgumentException
。
能够经过调用
这个方法,调用所须要的函数。
这样,在java中,咱们实际上可使用Method类做为方法的参数,进行回调!
.NET中也有相似的机制。
另外,C#有一个关键字delegate,委派,这实际上也就是方法的面向对象的对等物。委派的声明,实际上就是方法的声明。是
C中函数指针/参数回调机制的直接对应物。
Delegate和Java的Method其实是同一个东西。咱们能够用Method来模拟C#的委派。
为访问者模式正名
意图
|
表示一个做用于某对象结构中的各元素的操做。它使你能够在不改变各元素的类的前提下定义做用于这些元素的新操做。
|
适用性
|
- 一个对象结构包含不少类对象,它们有不一样的接口,而你想对这些对象实施一些依赖于其具体类的操做。
- 须要对一个对象结构中的对象进行不少不一样的而且不相关的操做,而你想避免让这些操做“污染”这些对象的类。Vi s i t o r 使得你能够将相关的操做集中起来定义在一个类中。当该对象结构被不少应用共享时,用Vi s i t o r 模式让每一个应用仅包含须要用到的操做。
- 定义对象结构的类不多改变,但常常须要在此结构上定义新的操做。改变对象结构类须要重定义对全部访问者的接口,这可能须要很大的代价。若是对象结构类常常改变,那么可能仍是在这些类中定义这些操做较好。
|
访问者模式,就是把一个类分红两个部分。一个部分是数据。把它们封装到一个类中。这种类常被叫作“数据容器类”。
另外一部分是对数据的操做。根据不一样的关注点,把函数分红一个或者几个接口。
在使用时,把接口和数据容器类组合起来使用。
最典型的访问者模式,就是著名的DAO数据访问对象模式。
把数据库表的字段用一个数据容器类封装起来。对类对象的操做,用一个DAO接口封装起来。
如对用户表数据的操做。建立2个类:
User类:
Integer id
String name
IUserDao接口
List<User> queryAll();
User query(Integer id);
Void delete(Integer id);
Void delete(User user);
Void reLoad(User user);
调用者:
UserService类:
IUserDao dao;
List<User> query (条件){
dao的各个函数调用。
}
这些DAO类,封装了一些函数,自己不保存数据。操做时全部的数据都在各个函数中经过参数传入。
访问者模式中的访问者接口,仅仅封装了多个函数,而没有数据,也能够看做是函数式编程的闭包。
调用访问者的函数的函数,就是函数式编程的高阶函数。
访问者模式,和命令模式同样,在OOP社区也饱受争议。一些程序员认为,访问者模式是罪大恶极,恶贯满盈!
原本一个好好的既包含数据,又包含操做数据的函数的类,被该死的访问者模式硬生生掰成2个甚至多个类。
数据容器类,只有数据,没有操做,那还能算是真正的类吗?!
访问者类,只有函数,没有数据,和命令模式一个样,能算是真正的类吗?!
实际上,在程序中,每每数据的结构是最稳定的。而操做数据的函数,因为业务上的缘由,是很是不稳定的。所以,访问者模式把数据和操做数据的函数分开。而且让访问者来访问数据。而数据并不知道访问者对象的存在和它们有哪些函数。(GOF提出的访问者模式中,访问者和被访问者<数据容器类>是互相关联的关系,我认为这样作很差。应该是访问者知道被访问者这样的单向关系。数据没有必要知道本身会被怎样操做。它只管保存数据和公开数据便可!)
并且,类不一样的用户,须要对数据进行的操做是不同的。不一样的用户,须要不一样的访问者函数。若是不使用访问者模式,那么全部用户将不得不获得做用于数据上的全部方法。而其中绝大部分是该用户并不须要的。这显然是浪费,逻辑上也说不过去。
如今,访问者模式和函数式编程也攀上了关系。若是OOP社群不要访问者模式,那么,咱们能够很差意思的说:其实…我用的是函数式编程:)
结论
兄弟,你用过命令模式,访问者模式,DAO模式吗?那么你已经在OOP语言中使用了函数式编程。
你写过只有函数,没有数据的类吗?那么你已经写过函数式编程了。
你用过Method类的invoke方法吗?当时你在用函数式编程。
你用过delegate吗?当时你也在用函数式编程。
你用过函数指针吗?当时你在用函数式编程。
类,必需要有数据吗?没必要!
类,必需要有函数吗?也没必要!