代码整洁之道_06对象和数据结构
第六章 对象和数据结构
- 对象和数据结构的区别是啥?
- 什么时候用对象,什么时候用数据结构?
- 面向对象还是面向过程?
1. 数据抽象
隐藏实现并非只是在变量之间放上一个函数层那么简单。
1 | //代码1:具象点 |
代码2的漂亮之处在于,你不知道该实现会是在矩形坐标系中还是在极坐标系中。可能两个都不是,然而,该接口还是明白无误地呈现了一种数据结构。
代码1暴露了实现,并且,即便变量都是私有的,通过取值器和赋值器使用变量,实现也被暴露了。
注意:隐藏实现并非只是在变量之间放上一个函数层那么简单。隐藏实现关乎抽象!类并不简单地用取值器和赋值器将其变量推向外界,而是暴露抽象接口,以便用户无须了解数据的实现就能操作数据本体(essence)。
2. 数据结构、对象的对立
- 对象把数据隐藏于抽象之后,暴露数据的函数;
- 数据结构暴露其数据,没有提供有意义的函数;
1 | // 代码3:过程式 形状代码 |
以上代码3
特征:
- 为
Geometry
类添加函数,不会影响形状类; - 若要增加形状,就得修改
Geometry
中的所有函数;
以下代码4
特征:
- 添加新形状,不影响现有函数;
- 添加新函数,所有形状都得做修改;
1 | // 代码4:多态式 形状代码 |
结论:
- 过程是代码:便于在不改动既有数据结构的前提下添加新函数;
- 面向对象代码:便于在不改动既有函数的前提下添加新类;
一切都是对象的说法只是一个传说——老练的程序员
3. The Law of Demeter
- 模块不应了解他所操作对象的内部情形。
- 对象不应通过存取器暴露其内部结构,因为这样更像是暴露而非隐藏其内部结构。
- 方法不应调用由任何函数返回的对象的方法。
3.1 火车失事
1 | final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath(); |
模块知道ctxt
对象包含多个选项,每个选项中都有一个临时目录,每个临时目录都有一个绝对路径。对于一个函数,这些知识够丰富了。调用函数需要懂得在如何在一大堆不同对象间浏览。
如果 ctxt、Options和ScratchDir
只是数据结构,没有任何行为,则它们自然会暴露其内部结构,Demeter Law 也就不适用了。
如果数据结构只简单地拥有公共变量,没有函数,而对象则拥有私有变量和公共函数,那么这个问题就没那么复杂了。
3.2 混杂
混杂:一半是对象,另一半是数据结构;
这种结构拥有执行操作的函数,也有公共变量或公共访问器及改值器,增加了添加新函数的难度,也增加了添加新数据结构的难度,两头不讨好。
3.3 隐藏结构
假使ctxt、Options和ScratchDir
是拥有真实行为的对象又怎样呢?由于对象应隐藏其内部结构,我们就不该看到内部结构。这样一来,如何才能取得临时目录的绝对路径呢?
如果ctxt
是一个对象,就应该要求它做点什么,而不该要求它给出内部情形。因此,我们看它获取绝对路径是要干啥:
1 | String outFile = outputDir + "/" + className.replace('.', '/') + ".class"; |
破案了:创建指定名称的临时文件!
那么,直接让ctxt
对象来做这事如何?
1 | BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName); |
4. 数据传送对象 DTO
最为精炼的数据结构,是一个只有公共变量、没有函数的类——数据传送对象(Data Transfer Objects,DTO)。
DTO是非常有用的结构,尤其是在与数据库通信或解析套接字传递的信息之类的场景中,在应用程序代码里一系列将原始数据转换为数据库的翻译过程中,它们往往是排头兵。
5. 小结
在任何系统中,希望能灵活地添加新数据类型,则使用对象;希望能灵活地添加新行为,则使用数据类型和过程。优秀的软件开发者不带成见地了解这种情形,并依据手边工作的性质选择其中一种适合的手段。