OOP的设计目标,实现手段和目标是否达成
OOP最重要的目标,是OCP,即「开闭原则」,对扩展开放,对修改关闭,
但是OCP不是免费的,教条主义泛滥,在不需要的地方使用OCP仅仅是浪费
继承的意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
接口规范带来的归一化的效果才是有用的, 但是归一化本身并非必须是OOP
Golang没有继承, smalltalk不用继承和接口也可以归一化, unix的泛文件概念也是归一化
业界存在太多误导和误解
一切皆对象实质上是在鼓励堆砌毫无意义的繁杂类设计,例如教条化的设计模式驱动编程
为了设计模式而设计模式并无意义
用面向对象语言写程序,和一个程序的设计是面向对象的,两者是八杆子打不着的两码事。
纯C写的linux kernel事实上比c++/java之类语言搞出来的大多数项目更加面向对象
OOP提供的所谓「保证旧代码不需要被修改」(其实也保证不了)是否有意义
面向对象编程有很多用途,很多用法,但是我们会发现「设计模式驱动」成为了非常有代表性的流派。
有时我们会疑惑,为什么会有设计模式,为什么面向对象编程会出现如此恐怖的这么复杂的类关系。
可能这是为了满足「增量式开发」。它假定几个前提:
1,将一份代码测试调试稳定所需要花的时间,远远大于撰写代码的时间。
2,已经通过测试的旧代码永远不修改,只对新代码进行新增,是最可靠的开发方式。
3,你经常需要重用没有源代码的库,并且扩展和修改其功能。
很多面向对象的设计,其实是为了满足一个很基本的目的:不修改旧代码,只新增代码。
代码只增不改,所以才会出现「继承」这种东西。因为你不需要原有的源代码也不需要修改原有的类,而是派生一个类增加新的方法。
「继承」的本来目的看起来就是为了解决这个问题。
因此,很多类层次关系的设计,不是为了更高的效率,不是为了代码看起来更清晰,而是为了「保证旧代码不需要被修改」这个目的。
不过,这是不是事实呢?在某些公司,这是事实,在很多公司,以上的假定不是事实。
1,很多代码并没有经过长时间的充分的测试,因而没有必要为了不浪费原有测试资源而拒绝修改旧代码。
2,修改旧代码在大多数公司是不可避免的。
3,很多时候我们提倡读懂库的源代码,而不会盲目使用无源代码的库。
4,现在流行的敏捷开发模型中宣传要拥抱变化。在敏捷模型中,测试案例是被固化的,而代码与架构都可以经常被修改,
换句话说,不修改旧代码,以及通过类层次关系设计系统架构,这些特性对敏捷来说都不重要,甚至背道而驰。
绑定数据和函数是否利于代码重用
面向对象得目标是大规模重用代码。
面向对象的手段是绑定数据和函数。
面向对象的哲学含义是给客体下定义来完成形式化抽象。 目的说白了就是为了尽可能重用代码来减少程序员工作。
但是矛盾的地方在于,真实世界中的客体的定义随着主体的看法而改变,换句话,不存在固定的形式化抽象,一切都要符合环境(上下文)。
最简单的例子就是,中文词的含义天天变,只有根据环境知识才能知道是什么含义。
而代码是为了具体问题而出现的,所以不存在通用抽象,也就不存在可以无限重用的定义和逻辑。
更加合理的设计理念应该还是 提供机制而不限制策略
所以从这一点上来看,提供一个常用的函数库要比精心设计复杂的对象模型要好的多。
更进一步,理想状态是全世界共享一种函数式语言,共享一份源代码,并对问题分类,
将所有函数按照问题进行整理,只有这样感觉才能彻底避免重新发明轮子。
OOP鼻祖Smalltalk与C/JAVA的不同
在 Smalltalk 里的“对象”实际上是一种高级的动态模块。
一个 Smalltalk 程序,是由一系列这样的高级动态模块构成的。
你可以在程序运行时(注意是运行时,而不是编译时)动态的修改、替换任意模块,而无需停止整个程序。
之所以在模块二字前加上“高级”和“动态”,实际上是对比其他非面向对象语言而言的。
例如 C 语言中你可以把 printf() 理解为一个控制台输出模块。
但是这样的模块,用起来容易,却不太灵活——如果你有实际的编程经验肯定能有所体会。
例如,你能够在程序运行的过程中,动态的替换掉 printf() 这个模块吗?
你能够动态的改变 printf() 这个模块的代码吗?
你能够动态的获取 printf() 这个模块的基本信息吗(参数列表,返回值甚至代码)?
别说,你还真的能,因为你可以通过各种技巧间接的实现这一点,但是这这是你的发明创造,而不是 C 语言本身的特性。
所以我们可以知道,C 语言对模块的支持仅仅止步于函数级别。
Smalltalk 的亮点就在于,它在语言层面引入了一种称为“对象”的高级动态模块系统。
一个 Smalltalk 程序由一系列的高级动态模块构成,每个模块之间通过通信进行协同。
也就是说,Smalltalk 所秉承的面向对象思想使得整个软件系统的可分割性和可组合性迈上了一个新台阶。这是面向对象思想的光辉所在。
现在我们回过头来看看 C++ 和 Java 中的面向对象。
事实上,C++ 和 Java 在实现面向对象的路途上遇到的第一道坎是他们本身都是静态类型的语言。
也就是说,这类语言的设计信条是一切结构皆须预先描述,因为编译器要检查。
于是没什么悬念的的就走上了 Class-based OOP 这条路(另一条路是 Prototype-based OOP)。
Class-based OOP 的一个特征是对象的结构需要预先声明,并且在运行过程中不允许改变——
C++ 和 Java 的作者有一千个理由这么干,最基本的原因就是性能考虑——但这样做的代价首先就削弱了系统的动态性。
更糟糕的是,C++ 和 Java 中,连对象的可替换性也需要预先声明。
我这么说一部分朋友可能没办法马上反应过来。其实就是说,在 Smalltalk 中,我们可以用任何一个对象随意替换掉另外一个,只要他们对外界而言行为一致,那么系统依然可以正常运行,
这一点,在大家更熟悉的 Ruby、Javascript 等语言中,被称为 Duck-Type 概念。
在 C++ 和 Java 中,你不能随意找个对象 x 来替换掉另外一个对象 y。即使他们拥有完全相同的行为也不行。
因为 C++ 和 Java 是 Class-based OOP 所以连可替换性也需要预先声明!这种声明方式就是让无数人潸然泪下的——继承!
即使一个对象 x 和 y 的行为是完全一样的,你也不能用 x 去替换 y。允许你替换的唯一前提是,x 被声明为继承自 y 的。
简单的来说,“继承”是一人分饰两角的典型——它既作为代码复用的一种手段,同时又成为了可替换性的一种声明。
这种设计非常失败,难以使用到直接导致了面向对象在 C++ 和 Java 中成为了一个阉割后的太监。
为了弥补继承的这种缺陷,于是引入了 Interface (只表明可替换性,不复用代码),
但这也改变不了什么了。毕竟 Interface 竟然也开始互相玩起了继承的游戏……
OO的弊端就是:设计抽象和封装的时间远远超过你解决问题的时间。
软件工程里对一个大型项目而言这样其实已经背离了设计规则,不是将一个大问题进行分解,而是将它放到一个更大的一个问题里,
你会发现OO就是不断地重构,重构,你会重新思考接口,重新改进设计模式,等等,一切,而却忽略了真正解决问题。
我曾一度觉得OO是大师们的智慧结晶,其实可以说这些是哲学的产物吧。等你发现,早已深陷其中。
还是要从实际解决问题入手!
静态的OO结构难以应对变化
我举一个具体的例子。假如一个组织的组织架构是静态的,那么OO方法足以描述这个组织,也可以在架构和人员不变的前提下定义好正常的公文流转等业务流程。
只要组织里的人、职责是固定的,那么用OO就足以按照组织机构树和职责进行分而治之。
但是,世界上不存在这么完美的事情。组织里某个人可能要请假造成公文无法流转,有人可能要临时身兼数职、有些公文突然会要求会签...
你马上会发现,看起来严谨的组织机构树和职责定义面对这么多的“临时状况”毫无应变的灵活性,而“临时状况”从上线第一天起就没有停过。
显然,OO的方法学在面对不断变化且超出设计预期的“状态”时非常非常力不从心。
(在OO的年代,我也尝试通过定义复杂状态机的方法来应对这个问题,但是同样地,设计阶段枚举得好好的状态,到了真实业务运转时完全走了样。)
所以,对于不断变化的“状态”我们必须引入新的“分而治之”的方法,
这就是为什么函数式编程(Functional Reactive Programming)、流编程(Unix或者Node.JS中的 Stream)现在越来越多地被应用在设计中的原因。
描述静态关系时,OO仍然是有用的,但是在应对复杂变化且相互变化的状态时,只有函数式编程方式可以让状态之间松耦。
性能
由于内存墙,内存存取成为现代计算机性能的重要瓶颈,
现时的OOP编程有可能不缓存友好(cache friendly),导致有时候并不能发挥硬件最佳性能
过度封装,多态,OOP的数据布局都会增大cache miss的可能性
Joe Armstrong的观点
- 数据结构和函数不应该被绑定到一起.这是最根本的错误,函数和数据完全不同,函数将输入转为输出,数据结构就是数据的结构,绑定到一起限制了它们的使用.
- 什么都必须是对象.连Time都必须是一个对象,Erlang里Time并不是对象.
- 数据类型定义散落在各个类里.Erlang或C可以在一个头文件里定义数据类型
- 对象有私有状态.状态是一切罪恶的根源,OOP不是直面状态尽可能将状态带来的影响减到最小,而是隐藏状态
OOP流行只是因为炒作,因为它可以创造一个新的软件工业,赚更多的钱
参考链接
http://www.zhihu.com/question/20275578
http://harmful.cat-v.org/software/OO_programming/why_oo_sucks