再见面向对象编程?

一位有着10年面向对象语言的程序员对面向对象两大支柱继承和封装提出了自己的疑问,并由此认为可以向面向对象说再见了。

原文:
Goodbye, Object Oriented Programming — Medium

该文从继承 封装和多态几个方面进行了论证。

(1)首先,该文认为继承是为了重用,所以会导致一个继承一个,最后很多类会继承一个大类,本来使用继承是为了重用这个目地,却得到复杂的大类,这是香蕉猴子丛林问题(Banana Monkey Jungle)。

其实,通过继承实现重用只是达到重用的一个很小方面,这种为重用而使用的继承只有少数几个场合才可以使用,非常类似于接口和实现,子对象继承父对象,实际运行是依靠子对象,而在设计时使用父对象,这样,如果父对象可能有多个子对象,也不会对依赖父对象的各种依赖设计造成冲击,同时又对变化保持开放,因为子对象数量是开放的,没有限制的,只要有变化,再做一个子对象即可,这种方式在策略模式中得到很好体现。

所以,通过继承实现重用不是漫无目的滥用,而是遵循一定业务场景和模式的,如果不遵循设计模式使用面向对象编程语言,必然会造成误用。

该文提出继承造成的钻石问题,其实也是上述错误思路的一个结果,总是希望有一个跨领域边界的绝对正确高高在上的基础对象,这个基础对象是为了重用而总结出来的,比如PoweredDevice设备这个类,如果没有任何上下文,设备这个类基本没有任何含义,该文假设出两个子类扫描器和打印机继承这个设备,然后复印机又要继承扫描器和打印机,这种两层继承关系造成好像钻石形状的继承关系。

说不好听,这是“作”出来的结果,万事万物皆有界,如果忽视客观世界,纸上玩弄语言当然是自讨苦吃,对象的英文是Object,又意味是客体的意思,客观世界的对象,面向对象主要含义是面向客观世界的用语言表达出来,不能切分语言的“能指”和“所指”。

按照这种“作”思维拱出来的基类当然脆弱了,可以说是人为制造的全局真理模型,不符合自然法则。当然脆弱。

从另外一个方面说,继承使用是有限制的,应用于业务模型是适当的,但是如果应用在没有客观世界反映的非业务模型场合,无疑属于误用,因为在这些非业务模型场合,无疑是函数模型的天下,是数学模型的天下。

该文认为继承会带来层次不清问题,因为父子对象可以互换,实际上混淆设计和运行两个阶段,重用层次都是设计层面的,如果重用与层次使用得到,抽象出框架库包,这些框架库包也需要在运行时和子类一起运行。另外,现实世界是多维的,如果将这些多维压缩到单继承,实际是降维了,这也会导致层次不够清楚问题。

让我们回到原始问题,为什么要使用继承? 因为父对象代表忽略细节的抽象,而子对象代表充分细节的实际对象,如果我们这个忽略细节的抽象维度选择正确了,找到事物不变性规律关系,将很少变化的关系进行抽象,无疑这种不变和可变分离本身已经形成了良好的层次。

(2)该文对OOP的第二个问题封装也提出了质疑,对象封装了状态,防止外界不经过一定业务行为擅自修改。该文认为如果对象被多个对象引用,就导致多个对象会修改该对象内的状态。

比如A是一个代表状态对象,传递给B,B保护其它作为私有字段:


Class B{
private A a;

public B(A a){
this.a = a;
}


}

上述状态A传递入B被保护起来,但是通过B构造器传入,而调用B的其他类在构造B时同时也要构造A,所以,在这个环节A是不安全的。

这个问题单独看好像确实是一个问题,但是实际项目中依赖注射首先能解决这个问题,其次,状态是经常变化的,状态总是有一个初始状态,这个初始状态是通过配置等方式设定的,不是由程序设定的。必要时,状态的克隆也不是非常耗费性能。在DDD中,将状态封装为值对象进行复制或共享是常用的事。

如果了解DDD,DDD通过聚合根这种设计将状态保护得更加完备,保证了聚合内部状态变化的一致性,如同数据库多个关联表之间同时改变一样。

(3)该文最后对多态性提出了异议,认为接口实现才是真正多态实现,其实面向对象编程语言不只是支持接口,还支持多种多态实现,比如通过AOP或Mixin等方式实现,多态性体现了对变化开放的一种方式。

总之,面向对象语言用于结合面向对象的分析设计方法,作为对客观世界的业务跟踪和记录无疑是非常适合的,如果脱离业务分析,那么函数式语言是非常合适的,没有副作用,没有可变状态,进入了纯粹逻辑世界。

面向对象世界除了继承和接口以外,还有关联和聚合等组合方式,后两者才是面向对象世界的重要核心,也是经常使用的两种方式,继承是初学者容易误用的方式,将继承看成是面向对象编程的首要特点本身也是有误导的。

以上是本人的反驳一面之词,也可见另外一篇反驳的英文文章:
Lamenting the Death of Object-Oriented Programming. (Sigh) Again?

[该贴被banq于2016-08-05 19:57修改过]