推荐言
在创建高质量的代码中,类的设计往往是重点,优秀的类的设计可以限制代码的复杂度
因为本书出现的年代较早,程序员还只是基于子程序(面向过程)去思考问题,缺少面向对象的思考。而对于普通的java程序员来说,这种里面的设计原则很多时候在我们用controller、service、mapper层的时候就已经被很好的实践了。很多时候curdboy更多的复杂度体现在方法的设计上,所以这一章大可以快速过掉。但是如果你未曾接触过面向对象的语言,这里面的对类的讨论还是能启发你的思考。
笔记
- 使用抽象数据类型ADT(这里可以理解为类)的好处
- 可以隐藏实现细节
- 改动不会影响到整个程序
- 更容易提高性能
- 让程序的正确性更显而易见
- 程序更具自我说明性
- 无须在程序内到处传递数据
- 你可以像在现实世界中那样操作实体,而不用在底层实现上操作它
- 使用ADT的建议
- 把常见的底层数据类型创建为 ADT 并使用这些 ADT,而不再使用底层数据类型
- 把像文件这样的常用对象当成ADT
- 简单的事物也可当做ADT
- 不要让 ADT 依赖于其存储介质
- 在非面向对象的环境里如何解决多份数据实例的问题
-
做法1:每次使用 ADT服务子程序时都明确地指明实例。在这种情况下没有“当前字体”的概念。
-
做法 2:明确地向ADT服务子程序提供所要用到的数据。
-
做法3:使用隐含实例(需要倍加小心)。设计一个新的服务子程序,通过调用它来让某一个特定的字体实例成为当前实例
-
创建类的抽象接口的指导建议:
- 类的接口应该展现一致的抽象层次
- 一定要理解类所实现的抽象是什么
- 提供成对的服务
- 把不相关的信息转移到其他类中有
- 尽可能让接口可编程,而不是表达语义
- 不要添加与接口抽象不一致的公用成员
- 同时考虑抽象性和内聚性
- 谨防在修改时破坏接口的抽象
-
良好的封装的建议
- 尽可能地限制类和成员的可访问性
- 不要公开暴露成员数据
- 不要对类的使用者做出任何假设
- 避免使用友元类(friend class)
- 不要因为一个子程序里仅使用公用子程序,就把它归入公开接口
- 让阅读代码比编写代码更方便
- 要格外警惕从语义上破坏封装性
- 每当你发现自己是通过查看类的内部实现来得知该如何使用这个类的时候,你就不是在针对接口编程了,而是在透过接口针对内部实现编程了。如果你透过接口来编程的话,封装性就被破坏了,而一旦封装性开始遭到破坏,抽象能力也就快遭殃了。
- 留意过于紧密的耦合关系
-
”包含“的使用建议
- 包含是一个非常简单的概念,它表示一个类含有一个基本数据元素或对象。与包含相比,关于继承的论述要多得多,这是因为继承需要更多的技巧,而且更容易出错,而不是因为继承要比包含更好。包含才是面向对象编程中的主力技术。
- 通过包含来实现“有一个/has a”的关系
- 在万不得已时通过 private 继承来实现“有一个”的关系
- 警惕有超过约7个数据成员的类
-
”继承“的使用建议
- 继承的概念是说一个类是另一个类的一种特化(specialization)。继承的目的在于,通过“定义能为两个或更多个派生类提供共有元素的基类”的方式写出更精简的代码。其中的共有元素可以是子程序接口、内部实现、数据成员或数据类型等。继承能把这些共有的元素集中在一个基类中,从而有助于避免在多处出现重复的代码和数据
- 决定使用继承的决策
- 对于每一个成员函数而言,它应该对派生类可见吗?它应该有默认的实现吗?
- 这一默认的实现能被覆盖(override)吗?对于每一个数据成员而言(包括变量、具名常量、枚举等),它应该对派生类可见吗?
- 用public 继承来实现“是一个……”的关系
- 要么使用继承并进行详细说明,要么就不要用它继承给程序增加了复杂度因此它是一种危险的技术。
- 邋循 Liskov替换原则(Liskov Substitution Principle,LSP)(里氏替换)。
- 确保只继承需要继承的部分
- 不要“覆盖”一个不可覆盖的成员函数
- 把共用的接口、数据及操作放到继承树中尽可能高的位置
- 只有一个实例的类是值得怀疑的
- 只有一个派生类的基类也值得怀疑
- 派生后覆盖了某个子程序,但在其中没做任何操作,这种情况也值得怀疑
- 避免让继承体系过深。用那个“神奇数字 7士2”用来限制一个基类的派生类总数–而不是继承层次的层数–可能更为合适。
- 尽量使用多态,避免大量的类型检查
- 让所有数据都是 private(而非protected)正如 Joshua Bloch所言,“继承会破坏封装”(Bloch 2001)。当你从一个对象继承时,你就拥有了能够访问该对象中的 protected 子程序和 protected 数据的特权。如果派生类真的需要访问基类的属性就应提供 protected访问器函数(accessor function)。
-
为什么有这么多关于继承的规则
- 这一节给出了许多规则,它们能帮你远离与继承相关的麻烦。所有这些规则背后的潜台词都是在说,继承往往会让你和程序员的首要技术使命(即管理复杂度)背道而驰。从控制复杂度的角度说,你应该对继承持有非常歧视的态度。下面来总结一下何时可以使用继承,何时又该使用包含:
- 如果多个类共享数据而非行为,应该创建这些类可以包含的共用对象。如果多个类共享行为而非数据,应该让它们从共同的基类继承而来,并在基类里定义共用的子程序。
- 如果多个类既共享数据也共享行为,应该让它们从一个共同的基类继承而来,并在基类里定义共用的数据和子程序。
- 当你想由基类控制接口时,使用继承;当你想自己控制接口时,使用包含。
-
对“成员对象以及方法”的建议
- 让类中子程序的数量尽可能少
- 禁止隐式地产生你不需要的成员函数和运算符
- 减少类所调用的不同子程序的数量
- 对其他类的子程序的间接调用要尽可能少
- 一般来说,应尽量减小类和类之间相互合作的范围
- 尽量让下面这几个数字最小:
- 所实例化的对象的种类
- 在被实例化对象上直接调用的不同子程序的数量
- 调用由其他对象返回的对象的子程序的数量
-
为什么有这么多关于继承的规则
- 这一节给出了许多规则,它们能帮你远离与继承相关的麻烦。所有这些规则背后的潜台词都是在说,继承往往会让你和程序员的首要技术使命(即管理复杂度)背道而驰。从控制复杂度的角度说,你应该对继承持有非常歧视的态度。下面来总结一下何时可以使用继承,何时又该使用包含:
- 如果多个类共享数据而非行为,应该创建这些类可以包含的共用对象。如果多个类共享行为而非数据,应该让它们从共同的基类继承而来,并在基类里定义共用的子程序。
- 如果多个类既共享数据也共享行为,应该让它们从一个共同的基类继承而来,并在基类里定义共用的数据和子程序。
- 当你想由基类控制接口时,使用继承;当你想自己控制接口时,使用包含。
-
对“成员对象以及方法”的建议
- 让类中子程序的数量尽可能少
- 禁止隐式地产生你不需要的成员函数和运算符
- 减少类所调用的不同子程序的数量
- 对其他类的子程序的间接调用要尽可能少
- 一般来说,应尽量减小类和类之间相互合作的范围
- 尽量让下面这几个数字最小:
- 所实例化的对象的种类
- 在被实例化对象上直接调用的不同子程序的数量
- 调用由其他对象返回的对象的子程序的数量
-
对”构造函数“的建议
- 如果可能,应该在所有的构造函数中初始化所有的数据成员
- 用私用(private)构造函数来强制实现单件属性(singletonproperty)
- 优先采用深层复本(deepcopies),除非论证可行,才采用浅层复本(shallow copies),为了不确定的性能提高而增加复杂度是不妥的,因此,在面临选择实现深拷贝还是浅拷贝时,一种合理的方式便是优先实现深拷贝–除非能够论证浅拷贝更好。
-
创建类的合理原因
- 为现实世界中的对象建模
- 为抽象的对象建模
- 降低复杂度
- 隔离复杂度,无论复杂度表现为何种形态–复杂的算法、大型数据集、或错综复杂的通讯协议等–都容易引发错误。一旦错误发生,只要它还在类的局部而未扩散到整个程序中,找到它就会比较容易。
- 隐藏实现细节
- 限制变动的影响范围
- 隐藏全局数据
- 让参数传递更顺畅
- 建立中心控制点
- 让代码更易于重用
- 为程序族做计划
- 把相关操作包装到一起
- 实现某种特定的重构
-
应该避免的类
- 避免创建万能类(god class)
- 消除无关紧要的类
- 避免用动词命名的类
-
通过遵循下列编程标准来强制实施你的包:
- 用于区分“公用的类”和“某个包私用的类”的命名规则
- 为了区分每个类所属的包而制定的命名规则和/或代码组织规则(即项目结构)
- 规定什么包可以用其他什么包的规则,包括是否可以用继承和/或包含等这些变通之法
这也是展示“在一种语言上编程”和“深入一种语言去编程”之间区别的好例子。
-
复述
本篇主要还是围绕“如何创建一个更好的类”的主题进行讨论,从类的创建、接口创建、成员变量和方法,以及构造函数三个层次去讲,这些指导意见不只是建议而更多告诉了我们为什么这么做,不这么做背后会发生什么。
过程中让我印象比较深的是在继承小节中提到了”里氏法则”,还有一个就是“要格外警惕从语义上破坏封装性”,学会避免语法错误仅仅是个开始,接踵而来的是无以计数的编码错误,而其中大多数错误都比语法错误更难于诊断和更正。当我们需要用户去深入接口里面去了解方法怎么使用的时候,我们就已经破坏了他的封装了。
初次之外就是神奇的数字7±2了,他给我了一个简单的标准,让我去衡量类的复杂度,如果类的成员变量>5或者 类的派生关系超过7±2的限制了,那么这个类就相对复杂。
本章内容较为丰富,建议有能力的读者还是应当去读完本章,毕竟本篇还是比较简单的摘述了文章的内容