为什么面向对象糟透了?

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

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

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

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

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

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

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

Stack s = new Stack();

s.push(100);

s.push(200);

s.pop();

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

push(stack, 100);

push(stack, 200);

pop(stack);

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

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

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

2

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

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

640?wx_fmt=png640

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

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

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

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

640

640?wx_fmt=png

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

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

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

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

640

640?wx_fmt=png

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

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

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

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

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

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

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

(码农翻身老刘注:C语言实现面向对象的详情可以移步《

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

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

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

3

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

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

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

640

640?wx_fmt=png

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

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

640

640?wx_fmt=png

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

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

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

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

640

640?wx_fmt=png

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

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

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

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

参考资料:

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

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

《架构整洁之道》

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

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值