函数式编程 (Functional Programming) 和 面向对象编程 (Object Oriented Programming) 是两个主流的编程范式,他们有各自独特的闪光点,比如函数式编程的数据不可变惰性求值,面向对象编程的继承多态等。这些语言特性上的区别,可以参考之前的文章,这篇文章主要从实现相同功能的角度,来对比这两种编程范式,他们在实现上的逻辑是截然相反的

初步实现

在函数式编程中,代码逻辑通常是按照要做什么。而在面向对象编程中,通常是把代码逻辑抽象成 class,然后给这些 class 一些操作。这么说起来很抽象,用下面这个例子来详细说明。

假设我们要用 函数式编程 和 面向对象编程 来分别实现下面这些功能:

eval toString hasZero
Int
Add
Negate

表格左列 Int, Add, Negate 是三个变式 (Variant),eval, toString, hasZero 是三种操作,这里要做的是填满这个表格,分别实现三个变式的三种操作。

函数式编程实现

这里用 ML 来做函数式编程的实现,即使没用过这门语言,应该也能读懂大概意思。

datatype exp =Int    of int| Negate of exp| Add    of exp * expexception BadResult of stringfun add_values (v1,v2) =case (v1,v2) of(Int i, Int j) => Int (i+j)| _ => raise BadResult "non-values passed to add_values"fun eval e =case e ofInt _       => e| Negate e1   => (case eval e1 ofInt i => Int (~i)| _ => raise BadResult "non-int in negation")| Add(e1,e2)  => add_values (eval e1, eval e2)fun toString e =case e ofInt i          => Int.toString i| Negate e1   => "-(" ^ (toString e1) ^ ")"| Add(e1,e2)  => "("  ^ (toString e1) ^ " + " ^ (toString e2) ^ ")"fun hasZero e =case e ofInt i            => i=0| Negate e1   => hasZero e1| Add(e1,e2)  => (hasZero e1) orelse (hasZero e2)

在函数式编程中,先定义了一个数据类型 (datatype) 来表示 Int, Negate, Add,这样定义的目的是什么呢?举个表达式的例子:

  • Int 代表一个 int 的数据,比如 Int(2)
  • Negate 代表 Int 的负数,比如 Negate(Int(2)))
  • Add 代表两个 Int 相加,比如 Add((Int(2), Int(3))

然后再分别实现三个操作 eval, toString, hasZero:

  • eval 是给一个表达式求值,比如给 Negate 求值,eval(Negate(Int(2))) = Int(-2) ,给 Add 求值,eval(Add(Int(2), Int(3))) = Int(5)
  • toString 是把这个表达式输出成字符串,比如 toString(Add(Int(2), Int(3))) = "2 + 3"
  • hasZero 是判断表达式有没有 0。

再看刚刚这句话函数式编程的代码逻辑通常是按照要做什么,这里的主体是三个操作,eval, toString 和 hasZero,所以三个分别是一个函数,在函数里去实现三种变式怎么操作。

可以说,函数式编程式纵向的填满了上面的表格。

面向对象编程

这里用 Ruby 来实现。

class Exp
endclass Value < Exp
endclass Int < Valueattr_reader :idef initialize i@i = ienddef eval # no argument because no environmentselfenddef toString@i.to_senddef hasZeroi==0end
endclass Negate < Expattr_reader :edef initialize e@e = eenddef evalInt.new(-e.eval.i) # error if e.eval has no i methodenddef toString"-(" + e.toString + ")"enddef hasZeroe.hasZeroend
endclass Add < Expattr_reader :e1, :e2def initialize(e1,e2)@e1 = e1@e2 = e2enddef evalInt.new(e1.eval.i + e2.eval.i) # error if e1.eval or e2.eval has no i methodenddef toString"(" + e1.toString + " + " + e2.toString + ")"enddef hasZeroe1.hasZero || e2.hasZeroend
end

< 在 Ruby 里是继承的意思,class Int < Value 表示 Int 继承了 Value,Int 是 Value 的 Subclass。

可以看到面向对象编程组织代码的方式和之前的完全不一样。这里把 Int, Negate, Add 抽象成了三个 class,然后分别给每个 class 加上 eval, toString, hasZero 三个方法。这也是刚刚那句话的说法 面向对象编程把代码逻辑抽象成 class,然后给这些 class 一些操作,这里的主体是 Int, Negate, Add 这三个 class。

可以说,面向对象编程是横向的填满了上的表格。

通过这个对比,可以知道 函数式编程 和 面向对象编程 是两种相反的思维模式和实现方式。这两种方式对代码的扩展性有什么影响呢?

扩展实现

eval toString hasZero absolute
Int
Negate
Add
Multi

在上面那个例子的基础上,我们再加一行一列,增加 Multi 这个变式,表示乘法,增加 absolute 这个操作,作用是求绝对值。这会怎么影响我们的代码呢?

函数式编程

在函数式编程中,要增加一个操作 absolute 很简单,只要添加一个新的函数,不用修改之前的代码。但是要增加 Multi 比较麻烦,要修改之前的所有函数。

面向对象编程

和函数式编程相反的,在这里增加一个 Multi 简单,只要添加一个新的 class,但是增加 absolute 这个操作就要在之前的每一个 class 做更改。

选择用 函数式编程 还是 面向对象编程 的一个考量因素是以后将会如何扩展代码,对之前代码的更改越少,出错的概率越小。

Binary Methods

前面的对比,操作都是在一个数据类型上进行的,这里进行最后一个对比,一个函数对多个数据类型进行操作时,函数式和面向对象分别怎么实现。

Int String Rational
Int
String
Rational

这里要实现的是一个 add_values(x, y) 的操作,把两个数据相加,但是 x, y 可能是不同的类型的。

函数式编程

函数式编程的实现相对简单:

datatype exp =Int    of int| String of string| Rational of realfun add_values (v1,v2) =case (v1,v2) of(Int i,  Int j)         => Int (i+j)| (Int i,  String s)      => String(Int.toString i ^ s)| (Int i,  Rational(j,k)) => Rational(i*k+j,k)| (String s,  Int i)      => String(s ^ Int.toString i) (* not commutative *)| (String s1, String s2)  => String(s1 ^ s2)| (String s,  Rational(i,j)) => String(s ^ Int.toString i ^ "/" ^ Int.toString j)| (Rational _, Int _)        => add_values(v2,v1)| (Rational(i,j), String s)  => String(Int.toString i ^ "/" ^ Int.toString j ^ s)| (Rational(a,b), Rational(c,d)) => Rational(a*d+b*c,b*d)| _ => raise BadResult "non-values passed to add_values"

这里的操作是 add_values,所以只要把所有可能的数据类型(总共9种)都列出来,就可以了。

面向对象编程:二次分派

按照上面面向对象编程的例子,我们可以这么做:

class Int < Value...def add_values vif v.is_a? Inti + v.ielsif v.is_a? MyStringi.to_s + v.ielse...endend
endclass MyString < Value...
end

在 add_values 这个方法里面去做判断,看传入参数的类型,去做相应的操作。这种做法不是那么的 面向对象,可以有另外一种写法:

class Int < Value...# double-dispatch for adding valuesdef add_values v # first dispatchv.addInt selfenddef addInt v # second dispatch: other is IntInt.new(v.i + i)enddef addString v # second dispatch: other is MyString (notice order flipped)MyString.new(v.s + i.to_s)enddef addRational v # second dispatch: other is MyRationalMyRational.new(v.i+v.j*i,v.j)end
endclass MyString < Value...# double-dispatch for adding valuesdef add_values v # first dispatchv.addString selfenddef addInt v # second dispatch: other is Int (notice order is flipped)MyString.new(v.i.to_s + s)enddef addString v # second dispatch: other is MyString (notice order flipped)MyString.new(v.s + s)enddef addRational v # second dispatch: other is MyRational (notice order flipped)MyString.new(v.i.to_s + "/" + v.j.to_s + s)end
end
...

这里涉及到了一个概念 二次分派 (Double Dispatch),在一次方法的调用过程中,做了两次 动态分派 (Dynamic Dispatch) 。用例子来说明

i = Int.new(1)
s = MyString.new("string")
i.add_values(s)

i.add_values(s)在调用这个方法时,实现了一次 dispatch,到 add_values 这个方法里后,做的其实是 s.addInt i,也就是去调用了 MyString 里的 addInt 这个方法,这是第二次 dispatch,所以叫做 double dispatch。

总结

函数式编程 和 面向对象编程 对比下来,我们并不能说哪一种模式更好。但是可以看出它们在思维上是截然不同的。函数式编程中侧重要做什么,面向对象编程侧重对象的抽象化,在有些编程语言里,比如 Java,是都可以实现的,但是要用哪种还要根据需求具体考虑。如果要了解更多 函数式编程 和 面向对象编程 的基础概念的话,可以看看之前的这三篇文章。

推荐阅读:
编程语言的一些基础概念(一):静态函数式编程
编程语言的一些基础概念(二):动态函数式编程
编程语言的一些基础概念(三):面向对象

函数式和面向对象编程有什么区别?相关推荐

  1. php 取对象数据_过程式编程和面向对象编程有什么区别?怎么理解php对象的概念?...

    PHP编程中对象的概念? 在上一篇文章咱们介绍了使用了php中的new pdo来连接数据库MYsql,其实在我们学习编程语言时经常会碰到"对象"这个词汇,准确来说是"面向 ...

  2. javascript中的面向对象_面向对象和函数式编程的本质区别

    编程的本质 当写过许许多多程序后,接触了那么多编程模式.设计模式.框架.语言.算法.数据结构以后,就会发现编程的本质万变不离其宗就是,操纵一坨数据.当然操纵的方式有许多,存储的方式也五花八门,但是本质 ...

  3. JavaScript 的函数式编程与面向对象编程区别在哪?

    本文通过代码来看一看JavaScript中函数式编程和面向对象编程的差异. 作者 | Jesse Warden 译者 | 弯月,责编 | 郭芮 出品 | CSDN(ID:CSDNnews) 以下为译文 ...

  4. 函数式编程与面向对象编程的区别

    定义 函数式编程:以函数思维做为核心,在这种思维的角度去思考问题.这种编程最重要的基础是λ演算,接受函数当作输入和输出. 面向对象编程:这种编程是把问题看作由对象的属性与对象所进行的行为组成.基于对象 ...

  5. 不是单组分组函数_面向对象编程是否已淘汰?函数式编程的枪口瞄错了对象

    全文共3838字,预计学习时长10分钟 图源:unsplash 编程在上世纪60年代遇到了一个大问题:计算机那时还没有那么强大,需要以某种方式在数据结构和进程之间分配容量.这意味着如果拥有大量数据,那 ...

  6. 【DS with Python】Matplotlib入门(一):架构概述、面向对象编程绘图与函数式绘图基础

    文章目录 前言 一.Matplotlib架构概述 1.1 Backend(后端) 1.2 Artist(美工) 1.2.1 Figure.Subplot与Axes 1.2.2 Axis.ticks与l ...

  7. 高阶函数||编程范式: 命令式编程/声明式编程 || 编程范式: 面向对象编程(第一公民:对象)/函数式编程(第一公民:函数)

    编程范式: 命令式编程/声明式编程 编程范式: 面向对象编程(第一公民:对象)/函数式编程(第一公民:函数) 高阶函数 filter/map/reduce filter中的回调函数有一个要求: 必须返 ...

  8. 面向过程与面向对象编程的区别和优缺点

    ■面向过程与面向对象编程的区别 转载至:https://www.cnblogs.com/strivers/p/6681876.html 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步 ...

  9. 问题小结(二)——maven的核心功能、面向对象编程和面向接口编程的区别、抽象类和接口的区别等

    文章目录 1. Java创建对象有哪四种方式? 2. 什么是maven?maven的核心功能有哪些? 3. 什么是MVC?说说分层的好处. 4. Spring的两大核心技术是什么? 5. 什么是IOC ...

最新文章

  1. python scipy卷积 图像卷积
  2. java安全管理器视频_java安全-安全管理器
  3. 做 NLP 算法研究,去大公司还是创业公司?
  4. 剑网服务器维护,12月31日服务器例行维护公告
  5. linux lnmp15 部署laravel项目
  6. 论文浅尝 | 从知识图谱流中学习时序规则
  7. 团队任务3 每日立会
  8. PHP Composer 新漏洞可引发大规模供应链攻击
  9. FX5 C的编程语言,三菱FX5-C32EX/D手册FX5-C32EX/D编程手册 - 广州凌控
  10. Windows键盘上的截屏按键PrtSc
  11. 【软工】week3-个人阅读作业-软件案例分析
  12. 1、AD创建模板和导入
  13. foxmail超大附件密码不对的解决办法
  14. c语言程序运行结果怎么看,c语言程序的运行结果.ppt
  15. 画业务逻辑流程图后的感想
  16. libero-soc许可证申请和环境配置
  17. iOS 警告 Local declaration of 'XXX' hides instance
  18. Elasticsearch实战——地理位置查询
  19. SMDS:交换式多兆位数据服务--网络大典
  20. 2007年9月1日御夫座流星雨

热门文章

  1. 【Android】【功能设计】离线数据同步方案
  2. joomla后台组件菜单设置
  3. 交通工程专业的计算机论文,交通工程专业的论文
  4. 撕裂者3990x和i9 10900k 哪个好
  5. linux命令之file命令
  6. 前端页面添加随机语录
  7. 2021年加氢工艺找解析及加氢工艺作业考试题库
  8. Bootstrap CSS入门初步学习---导航栏设计、代码展示
  9. 西南大学网络教育计算机网络,西南大学网络与继续教育学院(0087)《计算机网络》限时...
  10. Java霸王大陆3.0.2_三国志2霸王的大陆最终版