面向对象编程:这里我说了算!
英文原文:I give the orders around here!
自从 9 岁那年得到第一台 Commodore 64 家用电脑起,我就开始编程。然而,当面对如何写出好的代码时,我仍然感觉自己还有很多要学的。
在探索如何提高自己的过程中,我学了很多种语言。大多数是以面向对象为主的(OO)。
然而,让我惊讶的是,在我读过的大多数书本、杂志和网上文章中,有着大量遭透了的被当作面向对象例子的代码。
这些代码中,我看到的最多被违反的原则是“命令,不要去询问(Tell, Don’t Ask)”原则。这个原则讲的是,一个对象应该命令其它对象该做什么,而不是去查询其它对象的状态来决定做什么(查询其它对象的状态来决定做什么也被称作‘功能嫉妒(Feature Envy)’)。在面向对象的编程中,一个对象被定义成由对象状态和操作这个状态的方法组成。
在《Holub on Patterns: Learning Design Patterns By Looking At Code》这本书里,Allen Holub 在第一章里有一节的标题是“为什么 getter 和 setter 方法有害”。他在 JavaWorld 上的一篇文章里也谈论了这个问题。对所有的面向对象的程序员来说,这应该是一篇“必读”文章。
我有一些程序员同事,他们在一个对象上第一步声明了属性后,第二步就是添加 getter 和 setter 方法。JavaBean 规范对于这种文化的推广负于很大的责任。人们认为这是一种能让你写出可复用的模块化组建的好方法,但这已是很多年前的事了,时过境迁。
写带有 getter 和 setter 方法的类会导致过程式的代码。通过 getter 和 setter 来获取数据进行操作的逻辑最终会遍布整个应用,进而经常导致应用内的重复(这违反了另外一个原则:DRY——不要自我重复(Don’t Repeat Yourself))。这会致使产生很难维护的代码,当你对一个类做任何修改时,都会在整个应用内造成连锁式的牵连。
用这种方式来暴露数据还会妨碍你重构你的类,因为对这样的属性的任何修改都意味着会影响到访问了这个属性的其它类。
违反“命令,不要去询问”原则的另外一个副作用是,你的探询最终变成严重依赖状态信息并带有很多前提条件。这会让人很难理解你究竟询问的是什么。
你很可能会最终违反的第三个原则是,尽少知道(Least Knowledge)原则,也叫做得墨忒耳定律(Law Of Demeter)。这个定律可以总结为下面一句好:
一个类应该只跟它的直接朋友通话,不要跟陌生人说话。
在类里面加入 getter 方法,你的代码最终会写成这样:
if (person.getAddress () .getCountry () == "Australia") {
这违反了得墨忒耳定律,因为这个调用者跟 Person 过于亲密。它知道 Person 里有一个 Address,而 Address 里还有一个 country。它实际上应该写成这样:
if (person.livesIn ("Australia")) {
这并不违反得墨忒耳定律,因为这个调用者只跟它的直接朋友 Person 通话,而且它不知道内部情况。从“命令,不要询问”的视角来看,这也是更好,因为确定这个 person 是否居住在 Australia 的逻辑被隐藏到了 Person 里,如果我们改变 Person 里存储国家信息的方式,这将不会影响任何依赖这个信息的其它类。
另外一个这样写代码的好处是,它把我们的意图显示的很清楚。这是我会在以后的时间里讨论的另外一个话题。
为了避免违反“命令,不要去询问”原则,有几样事情你要记在心里。
- 在 IDE 里敲击快捷生成键前,询问自己’五个为什么’,问问自己为什么一上来就添加 getter 和 setter 方法。
- 不要询问对象的状态。要做什么,告诉它们该怎么做。
- 操作所属的对象拥有这些操作的数据。
进一步阅读
我的一个朋友是这个思想的大力倡导者,他用 East Oriented 方式精彩的解释了这个原则。
《Tell Don’t Ask and the effect on testing》,出自 Steve Freeman 和 Nat Pryce,他们也是 《Growing Object-Oriented Software》,《Guided by Tests》 两本书的作者。