最近一口气读完了二百多页的《Elegant Objects》。可能因为整理自博客所以排版一般,而且才二百多页定价却40多刀。但读过之后发现超值,甚至还想去买第二卷。作者观点大多比较激进,对自己的理念异常坚定,所以经常使用诸如“绝对不要使用XXX”、“记住XXX,就这样,句号”。但作者绝不故弄玄虚,在批判之后,一定会给出自己的建议和代码示例。除去个别章节个人觉得很有争议,大部分内容读过之后都是很震撼的。另外原书观点较为零散,为了方便感兴趣的同学继续阅读学习,本文重点部分都标注了与原书章节的对应。
1.面向对象思想
面向对象思想与此前“远古”时期编程思想的不同就是:我们站在更高的抽象上思考问题,从问题领域出发定义概念和概念间的关系,而不再是以机器指令为中心,想尽各种办法指挥机器去做事。作者极力想要扭转我们的思想,不要被机器同化,总是从它的角度来思考问题,而是忘记具体的底层架构、指令、编程语言,让以我们要解决的问题为中心。
此外书中作者反复强调的一点就是:一切都是为了可维护性。这可能是工程里的编程与科学里的计算的主要区别之一。作为软件工程的一环,除了基本的正确性外,最重要的一点就是可维护性了。因为现实世界的不确定,需求的反复变化,代码量巨大等问题,没有良好的可维护性的话。以可维护性为中心出发,作者指出了面向对象的三大癌症:可变性、静态方法、NULL。就像《黑客帝国》里说的人类是地球的癌症,这三者就是我们日常开发的混乱之源。而且某种程度上,三者背后的思想都根植于面向过程编程。
2.概念的封装
对象不是一堆由我们调用者决定调用顺序的函数集合,依次调用操作其内部封装的数据。作者反复用拟人的方式强调,这种指手画脚地方式是对“别人”的不尊重。我们去餐厅点菜,不会跟厨师说你得先做这个再做那个,这样做出来才好吃。现实中我们直接说,请给我来一份这个菜,厨师会自己决定如何烹饪。跟一个对象打交道也是如此,对象是一个自洽的、能自己做决定并自己行动的实体,就像一个能自给自足(self-sufficient)的生物一样。所以说,对象与对象之间相互“尊重信任”的关系是面向对象思想的核心。
2.1 设计者:隐藏好你的抽象
从对象设计者角度,要自重,不要泄漏你的实现细节,不要泄漏任何内部抽象。从这个角度来说,setter/getter也是邪恶的(原书3.5节)。你可能会说,setter/getter不是远好于直接暴露成员变量吗?而且它还是Java Bean的标准,我们还可以在里面添加数据验证、甚至改变存储内容方式等逻辑。但这都不重要,重要的是对于调用者来说这与直接的数据访问没什么区别。比如下面Cash
类中如果加上一个getDollars()
方法,那就好像让调用者对自己说:“去成员数据里找,看看有没有一个叫dollars
的,把它返回给我”。而dollars()
则会好一些,仿佛在说:“请告诉我你有多少美元?”(另可参考原书的2.4节 Choose method names carefully,作者对方法起名的见解)。
此外也不要返回NULL
造成空指针异常,失去调用者对你的信任(原书4.1节 Never return NULL)。返回NULL
的最常见情况就是找不到调用者想要的对象,那么可选的其他方式有返回空列表/集合,如果只是要返回一个单一对象的话则可采用空对象设计模式,或者在必要时抛出异常。更深层的原因是NULL
不应该出现在面向对象的世界里(原书3.3节 Never accept NULL arguments)。
也不要因为方便而对外提供成员常量,比如public static final String CRLF = "\r\n"
,看似很常见却破坏了面向对象的理念(原书2.5节 Don’t use public constants)。下面将这部分逻辑封装到CRLFString
类中,我们再也不用担心明天要为不同平台添加不同的换行符了。但这会导致很多类不是么,参见第三部分关于短小而简单的类设计理念的讨论。
class CRLFString {private final String src;CRLFString(String src) {this.src = src;}String toString() {if (/* this is windows */) {//...}return String.format("%s\r\n", src);}
}
2.2 使用者:Show Your Respect
从使用者角度,不要去偷窥,比如利用反射技术在运行时获取对象内部的信息,比如最常用的instanceof
判断对象是否是某个具体的子类等等(原书3.7节 Avoid type introspection and casting)。可能你会说你很少用反射,也可以尽量不用,但单元测试里的mock技术是无人不知、无人不晓的。mock难道不是很好的技术吗?现在已经有了很多非常棒的mock框架。
class Cash {private final Exchange exchange;private final int cents;public Cash(Exchange exchange, int cents) {this.exchange = exchange;