函数式和面向对象编程有什么区别?
函数式编程 (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,是都可以实现的,但是要用哪种还要根据需求具体考虑。如果要了解更多 函数式编程 和 面向对象编程 的基础概念的话,可以看看之前的这三篇文章。
推荐阅读:
编程语言的一些基础概念(一):静态函数式编程
编程语言的一些基础概念(二):动态函数式编程
编程语言的一些基础概念(三):面向对象
函数式和面向对象编程有什么区别?相关推荐
- php 取对象数据_过程式编程和面向对象编程有什么区别?怎么理解php对象的概念?...
PHP编程中对象的概念? 在上一篇文章咱们介绍了使用了php中的new pdo来连接数据库MYsql,其实在我们学习编程语言时经常会碰到"对象"这个词汇,准确来说是"面向 ...
- javascript中的面向对象_面向对象和函数式编程的本质区别
编程的本质 当写过许许多多程序后,接触了那么多编程模式.设计模式.框架.语言.算法.数据结构以后,就会发现编程的本质万变不离其宗就是,操纵一坨数据.当然操纵的方式有许多,存储的方式也五花八门,但是本质 ...
- JavaScript 的函数式编程与面向对象编程区别在哪?
本文通过代码来看一看JavaScript中函数式编程和面向对象编程的差异. 作者 | Jesse Warden 译者 | 弯月,责编 | 郭芮 出品 | CSDN(ID:CSDNnews) 以下为译文 ...
- 函数式编程与面向对象编程的区别
定义 函数式编程:以函数思维做为核心,在这种思维的角度去思考问题.这种编程最重要的基础是λ演算,接受函数当作输入和输出. 面向对象编程:这种编程是把问题看作由对象的属性与对象所进行的行为组成.基于对象 ...
- 不是单组分组函数_面向对象编程是否已淘汰?函数式编程的枪口瞄错了对象
全文共3838字,预计学习时长10分钟 图源:unsplash 编程在上世纪60年代遇到了一个大问题:计算机那时还没有那么强大,需要以某种方式在数据结构和进程之间分配容量.这意味着如果拥有大量数据,那 ...
- 【DS with Python】Matplotlib入门(一):架构概述、面向对象编程绘图与函数式绘图基础
文章目录 前言 一.Matplotlib架构概述 1.1 Backend(后端) 1.2 Artist(美工) 1.2.1 Figure.Subplot与Axes 1.2.2 Axis.ticks与l ...
- 高阶函数||编程范式: 命令式编程/声明式编程 || 编程范式: 面向对象编程(第一公民:对象)/函数式编程(第一公民:函数)
编程范式: 命令式编程/声明式编程 编程范式: 面向对象编程(第一公民:对象)/函数式编程(第一公民:函数) 高阶函数 filter/map/reduce filter中的回调函数有一个要求: 必须返 ...
- 面向过程与面向对象编程的区别和优缺点
■面向过程与面向对象编程的区别 转载至:https://www.cnblogs.com/strivers/p/6681876.html 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步 ...
- 问题小结(二)——maven的核心功能、面向对象编程和面向接口编程的区别、抽象类和接口的区别等
文章目录 1. Java创建对象有哪四种方式? 2. 什么是maven?maven的核心功能有哪些? 3. 什么是MVC?说说分层的好处. 4. Spring的两大核心技术是什么? 5. 什么是IOC ...
最新文章
- python scipy卷积 图像卷积
- java安全管理器视频_java安全-安全管理器
- 做 NLP 算法研究,去大公司还是创业公司?
- 剑网服务器维护,12月31日服务器例行维护公告
- linux lnmp15 部署laravel项目
- 论文浅尝 | 从知识图谱流中学习时序规则
- 团队任务3 每日立会
- PHP Composer 新漏洞可引发大规模供应链攻击
- FX5 C的编程语言,三菱FX5-C32EX/D手册FX5-C32EX/D编程手册 - 广州凌控
- Windows键盘上的截屏按键PrtSc
- 【软工】week3-个人阅读作业-软件案例分析
- 1、AD创建模板和导入
- foxmail超大附件密码不对的解决办法
- c语言程序运行结果怎么看,c语言程序的运行结果.ppt
- 画业务逻辑流程图后的感想
- libero-soc许可证申请和环境配置
- iOS 警告 Local declaration of 'XXX' hides instance
- Elasticsearch实战——地理位置查询
- SMDS:交换式多兆位数据服务--网络大典
- 2007年9月1日御夫座流星雨
热门文章
- 【Android】【功能设计】离线数据同步方案
- joomla后台组件菜单设置
- 交通工程专业的计算机论文,交通工程专业的论文
- 撕裂者3990x和i9 10900k 哪个好
- linux命令之file命令
- 前端页面添加随机语录
- 2021年加氢工艺找解析及加氢工艺作业考试题库
- Bootstrap CSS入门初步学习---导航栏设计、代码展示
- 西南大学网络教育计算机网络,西南大学网络与继续教育学院(0087)《计算机网络》限时...
- Java霸王大陆3.0.2_三国志2霸王的大陆最终版