为什么面向对象糟透了?

开发 开发工具
又是周末,编程语言“三巨头”Java, Lisp 和C语言在Hello World咖啡馆聚会。

 又是周末,编程语言“三巨头”Java, Lisp 和C语言在Hello World咖啡馆聚会。

[[277696]]

服务员送来咖啡的同时还带来了一张今天的报纸, 三人寒暄了几句, C语言翻开了报纸,突然眼前一亮:“这篇文章的标题写得好啊, 《为什么面向对象糟透了!》”

(这是一篇很早之前的文章,作者是大名鼎鼎的Joe Armstrong , 也就是Erlang之父。)

Java 大吃一惊,居然有人这么骂面向对象?!

他赶紧抢过来,看了一会儿,说到:“虽然我对去世的Joe Armstrong老先生非常尊敬,但是我对他的观点却不敢苟同,你看他说'数据结构和函数不应该绑到一起!'”

C语言说:“他说得很有道理啊,函数是实现算法的,就像一个黑盒子,只要理解了它的输入和输出,就理解了它的功能,而数据结构呢就是单纯的‘声明’,为什么要把他们绑在一起呢?”

“不不不,还是绑在一起好!我给你举个例子,一个栈,如果你把它当成一个完整的对象,那用起来就方便多了。”

  1. Stack s = new Stack(); 
  2. s.push(100); 
  3. s.push(200); 
  4. s.pop(); 

C语言不甘示弱:“把数据结构和函数分开也挺好啊!比如我可以创建一个叫做Stack的数据结构,然后写几个对这个数据结构操作的函数。”

  1. push(stack, 100); 
  2. push(stack, 200); 
  3. pop(stack); 

Java不屑一顾地说:“你看看你这种方式多丑陋啊。”

C语言寸步不让:“本质都是一样的,你是o.f() ,我是f(o), 有啥区别?”

Lisp也插了一嘴:“还有我的(f o) ”

2

Java无语,心说这两个家伙就是胡搅蛮缠。突然,他心中一动:我怎么忘记多态了。

Java说:“本质是不一样的,你要知道,o.f()是可以产生多态行为的,这就带来了巨大的好处,我给你举个例子,你有一段业务逻辑,需要把计算的结果记录到文件中,将来还可能会记录别的地方,你的设计可能是这样的。”

[[277698]]

 

C语言:“难道不应该如此吗?一个函数调用另外一个函数?”

Java 说到:“这里有个依赖的问题,就是businessLogic()不但运行期依赖writeToFile(), 在源码级或者编译期也会依赖。”

C语言:“这不是很正常嘛!”

Java又说道:“不一样, 写入文件是底层的实现细节,不是高层策略,假如用户不想把计算的结果保存到文件中了,而是想通过邮件发送,那你的businessLogic也得修改了,对不对?”

[[277698]]

 

C语言:“那肯定啊!函数调用嘛,一个改了,另外一个也得改。”

“这就是问题了, 编译期/源代码的依赖导致我们没法把系统划分成独立的组件,各自独立开发,独立部署,一个的变化就影响到了另外一个。”

C语言觉得有一定的道理,他说:“那怎么办?”

Java说:“你看看我使用多态以后的设计,我的业务逻辑在编译时只依赖那个接口Writer,而不依赖具体的实现FileWriter和MailWriter。”

 

“你的意思是只要接口Writer不变化,底层的具体实现如FileWriter ,MailWriter可以随意变化,随意替换,就像插件一样,对吧?” C语言说到。

“对啊,编译期/源代码不依赖,运行期依赖,这就是延迟绑定带来的好处,现在你明白o.f()和 f(o)的本质区别了吧。 ”

Lisp 不失时机又插了一嘴:“你那接口中只有一个函数,就是write(),用什么接口啊,脱裤子放屁,多此一举,在我这儿只要把不同的函数传递过去就可以了。”

Java笑道:“别抬杠,这就是个简单的例子,不管是用接口,还是传递函数,都是延迟绑定嘛, 关键点都是要找到那个稳定的东西(Writer),就是抽象。你找不到这个稳定的东西,做不出抽象,你的系统就没法划分成可以独立开发,独立变化的组件了。”

C语言还想反击,但一直找不到突破口。

Lisp说道:“别听Java在哪里忽悠,C老弟,你也能实现运行期的延迟绑定,这不是Java的专利,你忘了虚函数表了?”

C语言一拍大腿:“是啊,我忘了那一年春节回家,Linus 大神曾经告诉我,虚函数表和函数指针才是实现多态的关键,比如Unix/Linux把设备都当成了文件,有标准的open , read 等方法,对于不同的设备,都能调用对应的方法, 那是怎么实现的?也是通过虚函数表做延迟绑定嘛!”

C语言高兴了:“哈哈,Java 老弟,看来我们本质上还是一样的,多态只不过是函数指针的一种应用!”

Java说:“所以编程的关键不在于是否使用了面向对象的语言,这一点你同意吧?”

C语言点头,编程的关键点就是找到、抽象出稳定的接口,针对这个接口编程,这样就可以让各个模块能够独立地变化。

3

“说起来容易,做起来难,这儿有一个例子,你给我用面向对象设计一下?” Lisp 抛出了一道题。

动物可以分为肉食动物,草食动物,水生动物,陆生动物,用类如何表示?

Java 说:这还不简单,看看这名词多明显啊,都可以变成类啊,让他们都继承动物就可以了。

 

Lisp看到Java 掉入了陷阱,狡黠地一笑:“那有的动物就既是陆生动物,又是肉食动物,怎么表达?”

“那我就加一个陆生肉食动物类。” 虽然觉得不妥,Java还是说了出来。

 

“那要是再来一个水生肉食动物怎么办?或者来了一个新的概念‘哺乳动物’, 该怎么处理?”

“哈哈哈,我懂了,随着需求的增多,不但类会出现爆炸, 还可能会出现那种怪异的类,这面向对象编程确实是有大问题啊!” C语言说。

Java 低头沉思不语,突然,脑海中想起来了那句话:优先使用组合而不是继承。

怎么使用组合?必须得改变下看待问题的方式,对,应该这样:

 

 

[[277698]]

 

 

Java得意地说:“看看这个图, 动物具备多个特性,如'进食', '移动',将来还可以加上'哺乳方式',每个特性都是一个接口,接口是稳定的, 动物这个概念是可以通过这些接口特性给组合起来的。”

Lisp 赞赏地点头, C语言向Java投去了钦佩的目光,这家伙经常做面向对象的设计,还是有两把刷子的,他通过特性的方式把变化给隔离了, 各个特性可以通过组合的方式,像插件一样随意替换, 嗯,这才是面向对象的真正精髓啊。

夜已深, 最后Java做了个总结,大伙散去。

“编程嘛就是发现变化,并且把它给隔离起来,使用各种语言都可以,面向对象的语言有着直接使用多态的便利,以后不要随随便便就diss它了。”

参考资料:

http://www.cs.otago.ac.nz/staffpriv/ok/Joe-Hates-OO.htm

《敏捷软件开发:原则,模式与实践》

《架构整洁之道》

《面向对象开发参考手册》

【本文为51CTO专栏作者“刘欣”的原创稿件,转载请通过作者微信公众号coderising获取授权】

 

戳这里,看该作者更多好文

责任编辑:武晓燕 来源: 51CTO专栏
相关推荐

2023-01-10 09:38:09

面向对象系统

2013-11-26 10:14:15

面向对象函数式

2011-07-05 16:15:49

面向对象

2020-08-03 07:50:56

存储对象存储

2019-11-11 10:48:44

面向对象语言

2013-03-11 09:23:22

Go语言面向对象

2014-12-17 09:57:39

2022-07-30 23:41:53

面向过程面向对象面向协议编程

2015-02-11 10:22:25

对象存储云共享S3存储

2017-04-21 09:07:39

JavaScript对象编程

2012-01-17 09:34:52

JavaScript

2013-08-21 17:20:49

.NET面向对象

2013-04-17 10:46:54

面向对象

2020-08-02 22:54:04

Python编程语言开发

2010-07-15 13:56:24

面向对象面向过程

2020-05-29 10:17:00

代码扩展设计

2012-06-07 10:11:01

面向对象设计原则Java

2012-02-27 09:30:22

JavaScript

2012-12-13 11:01:42

IBMdW

2010-07-06 09:43:34

UML面向对象
点赞
收藏

51CTO技术栈公众号