查看原文
其他

为什么面向对象糟透了?

Python猫 2022-04-12

The following article is from 码农翻身 Author 码农翻身刘欣

又是周末,编程语言“三巨头”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()是可以产生多态行为的,这就带来了巨大的好处,我给你举个例子,你有一段业务逻辑,需要把计算的结果记录到文件中,将来还可能会记录别的地方,你的设计可能是这样的。”



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


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


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


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


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语言实现面向对象的详情可以移步《C语言:春节回家,就我没有对象!》)


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


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


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




3




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


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


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


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


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


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


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


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


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


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


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


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


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


参考资料:

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

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

《架构整洁之道》

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


关于作者:

码农翻身老刘,15年以上软件开发经验,畅销书《码农翻身》作者,前 IBM 架构师,领导过多个企业应用架构设计和开发工作;洞察技术本质,擅长用故事去讲解复杂技术。

Python猫技术交流群开放啦!群里既有国内一二线大厂在职员工,也有国内外高校在读学生,既有十多年码龄的编程老鸟,也有中小学刚刚入门的新人,学习氛围良好!想入群的同学,请在公号内回复『交流群』,获取猫哥的微信(谢绝广告党,非诚勿扰!)~


还不过瘾?试试它们




为什么 Python 不用声明类型?

Python最佳代码实践:性能、内存和可用性!

面试官逼疯面试者:聊聊多进程场景下Logging的解决方案?

弃繁就简!一行代码搞定 Python 日志!

Python重大变化:是match-case,不是switch-case

Python优化机制:常量折叠


如果你觉得本文有帮助
请慷慨分享点赞,感谢啦

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存