类是由一组数据和子程序构成的集合,这些数据和子程序共同拥有一组内聚的、明确定义的职责。类也可以只是由一组子程序构成的集合,这些子程序提供一组内聚的服务,哪怕其中并未涉及共用的数据。成为高效程序员的一个关键就在于,当你开发程序任一部分代码时,都能安全地忽视程序中尽可能多的其余部分。而类就是实现这一目标的首要工具。
6.1 类的基础:抽象数据类型(ADTs)
抽象数据类型是指一些数据以及对这些数据所进行的操作的集合。
使用ADTs的益处:
- 可以隐藏实现的细节
- 改动不会影响到整个程序
- 让接口提供更多的信息
- 更容易提高性能
- 让程序的正确性更显而易见
- 程序更具自我说明性
- 无需再程序内到处传递数据
- 你可以像在现实世界中那样操作实体,而不用在底层实现上操作它
使用ADTs的指导建议:
- 把常见的底层数据创建为ADT并使用这些ADT,而不再使用底层数据
- 把文件这样的常用对象当成ADT
- 简单的事物也可当作ADT
- 不要让ADT依赖于其存储介质
6.2 良好的类接口
创建高质量的类,第一步,可能也是最重要的一步,就是创建一个好的接口。
创建类的抽象接口的一些建议:
- 类的接口应该展现一致的抽象层次
- 一定要理解类所实现的抽象是什么
- 提供成对的服务
- 把不相关的信息转移到其它类中
- 尽可能让接口可编程,而不是表达语义
- 谨防在修改是破坏接口的抽象
- 不要添加与接口抽象不一致的公用成员
- 同时考虑抽象性和内聚性
良好的封装:
- 尽可能限制类和成员的可访问性
- 不要公开暴露成员数据
- 避免把私用的实现细节放入类的接口中
- 不要对类的使用者做出任何假设
- 避免使用友元类
- 不要因为一个子程序里仅使用公用子程序,就把它归入公开接口
- 让阅读代码比编写代码更方便
- 要格外警惕从语义上破坏封装性
- 留意过于紧密的耦合关系
6.3 有关设计和实现的问题
包含(has a):
包含是一个非常简单的概念,它表示一个类含有一个基本数据元素或对象。包含是面向对象编程的主力技术。
- 通过包含实现“有一个(has a)”的关系
- 在万不得已时通过private继承来实现“有一个”关系
- 警惕有超过7个数据成员的类
继承(is a):
- 用public继承实现“是一个(is a……)”的关系
- 要么使用继承并进行详细的说明,要么就不要用它
- 遵循Liskov替换原则(派生类必须能通过基类的接口而被使用,且使用者无需了解两者之间的差异)
- 确保只继承需要继承的部分
- 不要“覆盖”一个不可覆盖的成员函数(即:派生类中的成员函数不要与基类中不可覆盖的成员函数重名)
- 把共用的接口、数据和操作放到继承树中尽可能高的位置
- 只有一个实例的类是值得怀疑的
- 只有一个派生类的基类也是值得怀疑的
- 派生后覆盖了某个子程序。但在其中没做任何操作,这种情况也值得怀疑
- 避免让继承体系过深
- 尽量使用多态,避免大量的类型检查
- 让所有数据都是private(而非protected)
多重继承:
为什么这么多关于继承的规则:
成员函数和数据成员:
构造函数:
6.4 创建类的原因
创建类的合理原因:
- 为现实世界中的对象建模
- 为抽象对象建模
- 降低复杂度
- 隔离复杂度
- 隐藏实现细节
- 限制变动的影响
- 隐藏全局数据
- 让参数传递更顺畅
- 建立中心控制点
- 让代码更易于重用
- 为程序族做计划
- 把相关操作包装到一起
- 实现某种特定的重构