《代码大全》读书笔记 6
条评论《代码大全2》读书笔记 第六章 可以工作的类
6 可以工作的类
6.1 类的基础:抽象数据类型(ADTs)
使用 ADT 的益处
- 隐藏实现细节。
- 增加功能和改动不会影响到整个程序。
- 让接口提供更多的信息。
- 更容易提高性能。
- 让程序的正确性显而易见。
- 程序更具有自我说明性。
- 无须在程序内到处传递数据。
- 你可以像在现实世界中那样操作实体,而不用在底层实现上操作它。
指导建议
- 把常见的底层数据类型创建为 ADT 并使用他们,而不再使用底层数据类型。
- 把像文件这样的常用对象当成 ADT。
- 简单的事物也可当作 ADT。
- 不要让 ADT 依赖于存储介质。
如何在非面向对象环境中使用 ADT 处理多分数据实例
- 做法一:每次使用 ADT 服务子程序时都传入实例的ID。(需要进行一次查询)
- 做法二:传入整个实例。(暴露了不需要的数据)
- 做法三:使用隐含实例,执行前调用
SetCurrentInstance(instID);
。(不推荐)
6.2 良好的类接口
好的抽象
- 类的接口应该展现一致的抽象层次。
- 一定要理解类所实现的抽象是什么。(一些类非常相像。)
- 提供成对的服务。(有开就有关。)
- 把不相关的信息转移到其他类中。
- 尽可能让接口可编程,而不是表达语义。(一个接口中任何无法通过编译器强制实施的语义部分,就是一个可能被误用的部分,需要用注释或者断言指出。)
- 谨防在修改时破坏接口的抽象。
- 不要添加与接口抽象不一致的共用成员。
- 同时考虑抽象性和内聚性。
良好的封装
- 尽可能地限制类和成员地可访问性。
- 不要公开暴露成员数据。
- 避免把私用的实现细节放入类的接口。
- 如果你甚至不想把
private
的字段放到头文件中暴露给其他人开,可以使用一个XXXImplement
类的指针。
- 如果你甚至不想把
- 不要对类的使用者作出任何假设。
- 多数场合下应避免使用友元类。
- 不要因为一个程序里仅使用公用子程序,就把它归入公开接口。
- 让阅读代码比编写代码更方便。
- 要格外警惕从语义上破坏封装性。
- 留意过于紧密的耦合关系。
6.3 有关设计和实现的问题
- 包含才是面向对象编程中的主力技术,而不是继承。
包含(Containment):... has a ...
- 包含用于实现
has a
关系。 - 万不得已时才通过
private
继承来实现has a
关系。 - 警惕有超过约7个数据成员的类。(7±2)
继承(Inheritance):...is a ...
- 继承用于实现
is a
关系。基类对派生类将会做什么既设定了预期,也给出了限制。 - 要么使用继承并进行详细说明,要么就不要用它。
- 遵循里氏代换原则(LSP,Liskvo Substitution Principle)。“派生类必须能通过基类的接口而被使用,且使用者无须了解两者之间的差异。”
确保只继承需要继承的部分。根据是否可覆盖、是否提供默认实现,可以分为:
- 抽象且可覆盖的子程序。
- 可覆盖的子程序。
- 不可覆盖的子程序。
不要覆盖一个不可覆盖的成员函数。
- 把共用的接口、数据及操作放到继承树中尽可能高的位置。
- 只有一个实例的类是值得怀疑的。(使用单件模式)
- 只有一个派生类的基类也是值得怀疑的。(提前设计)
- 派生后覆盖了某个子程序,但在其中没做任何操作(基类中有操作),这种情况也值得怀疑。(基类的设计问题。)
- 避免让继承体系过深。
- 尽量使用多态,避免大量的类型检查。(如果出现较为复杂的switch,就考虑一下多态。)
- 让所有数据都是
private
而非protected
。“继承会破坏封装”,如果真的需要私有数据,就提供protected
访问器函数。 - 慎用多重继承,设计良好的多重继承是能避免菱形继承的。
使用场景
- 如果多个类共享数据而非行为,应该共用对象,被这些类包含。
- 如果多个类共享行为而非数据,应该定义基类,被这些类继承。
- 如果多个类既共享数据又共享行为,应该定义基类,基类中定义共用数据和子程序,被这些类继承。
- 当你想由基类控制接口时,使用继承。
- 当你想自己控制接口时,使用包含。
成员函数和数据成员
- 让类中子程序的数量尽可能少。
- 尽量隐式地产生你不需要地成员函数和运算符。
- 减少类所调用的不同子程序的数量。
- 对其他类的子程序的间接调用要尽可能少。
一般来说,应尽量减少类和类之间相互合作的范围。包括:
- 所实例化的对象的种类。
- 在被实例化对象上直接调用的不同子程序的数量。
- 调用由其他对象返回的对象的子程序的数量。
构造函数
- 如果可能,应该在所有的构造函数中初始化所有的数据成员。(防御式编程)
- 用私有构造函数来强制实现单件属性。
优先采用深层复本(deep copies),除非论证可行,才采用浅层副本(shallow copies)。
- 深层:开发和维护较为简单,性能也往往不会损失太多。
- 浅层:更快,但是增加了复杂度,容易出错。
6.4 创建类的原因
创建原因
- 为现实世界中的对象建模。
- 为抽象的对象建模。
- 降低复杂度。
- 隔离复杂度。
- 隐藏实现细节。
- 限制变动的影响范围。
- 隐藏全局变量。(使用访问器子程序 access routine)
- 让参数传递更顺畅。
- 建立中心控制点。
- 让代码更易于重用。
- 为程序族做计划。
- 将相关操作包装到一起。
- 实现某种特定的重构。
应该避免的类
- 避免创建万能类。
- 消除无关紧要的类。
- 避免用动词命名的类。
6.5 与具体编程语言相关的问题
不同语言之间可能有差异的地方:
- 在继承层次中被覆盖的构造函数、析构函数的行为。
- 在异常处理时构造函数、析构函数的行为。
- 默认构造函数的重要性。
- 析构函数的调用时机。
- 覆盖运算符相关。
- 对象被创建和销毁时,或被声明时,或退出作用域时,处理内存的方式。
- 本文链接:http://ifanze.cn/2018/06/26/《代码大全2》(6)/
- 版权声明:转载请在评论区说明。