类的设计
抽象数据类型
抽象数据类型可以让你像在现实世界中一样操作实体,而不用关心其底层实现。 ADT的益处
- 隐藏实现细节
- 限制改动程序影响的范围
- 让接口提供更多的信息
- 让程序的正确性更显而易见,易于检查错误
- 程序更具自我说明性
- 无须在程序内部到处传递数据
- 像现实世界中那样操作实体,不用在底层操作它
注意事项 不要让ADT依赖于存储介质。在命名上须要注意。如操作税率表的RateFile类不如RateTable类的命名方式好,虽然目前是通过文件的方式实现的。
类优秀的设计
- 类的接口应该展现一致的抽象层次
- 一定要理解类所实现的抽象是什么
- 提供成对的服务 (如打开,关闭)
- 把不相关的信息移到其它类中
- 尽可能让接口可编程,而不是表达语义
- 谨防在修改时破坏类的抽象
- 不要添加与接口抽象不一致的公用成员
- 同时考虑类的抽象性和内聚性
良好的封装
- 尽可能地限制类和成员的可访问性
- 不要公开暴露成员数据
- 避免把私用的实现细节放入类的接口中 (C++应该使用信封类)
- 不要对类的使用者做出任何假设
- 避免使用友元
- 不要因为一个子程序里仅使用了公用子程序,就把它归入公开接口中
- 让阅读代码比编写代码更方便
- 警惕从语义上破坏封装性 (如在调用B之前要先调用A初始化数据,但是由于开发人员知道B中会去调用A,所以不调用A)
- 留意过于紧密的耦合关系
有关设计和实现的问题
包含关系
- 通过包含来实现有一个的关系
- 在万不得已情况下才使用private继承来实现有一个关系
- 数据成员应该控制5~9个
继承关系
- 用public继承来实现“是一个”的关系
- 使用继承时必须进行详细说明
- 遵循LSP原则(派生类完全可以通过基类的接口来使用,而使用者无须了解两者之间的差异)
- 确保只继承需要继承的部分
- 把共用的接口,数据和操作尽可能的放在更高的抽象层次上
- 只有一个实例的继承值得怀疑
- 只有一个派生类也值得怀疑(提前设计?)
- 派生类覆盖某个子程序,但在其中不做任何操作,值得怀疑
- 避免让继承体系过深
- 尽量使用多态,避免大量的类型检查
- 让所有数据都是private的
成员函数和数据成员
- 让类中的子程序数量尽可能少
- 禁止隐示的产生不需要的成员函数和运算符
- 对其它类的子程序调用尽可能少
- 尽量减少类和类之间的合作范围
所实例化的类的数量 调用其它类的子程序的数量 调用其它对象返回对象的子程序的数量
构造函数
优化采用深层Copy而不是浅Copy,除非论证浅Copy的可行性。 浅Copy虽然会提升性能,但会加大管理的复杂度,过早代码性能优化是没有意义的,后期可以很容易的把深Copy重构为浅Copy
创建类的原因
每次创建类时,都应该问一下自己创建这个类的原因。
- 为现实世界中的对象建模
- 为抽象的对象建模
- 降低复杂度
- 隔离复杂度
- 建立一个控制中心
- 限制变动的范围
- 隐藏实现的细节
- 隐藏全局数据
- 让参数传递更顺畅
- 让代码容易重用
- 把易变的区域封装
- 把相关操作包装到一起
- 实现某种特定的重构
核对表
抽象
- 类是否有一个中心目的,类的命名是否符合这个中心目的?
- 类的接口是否展现一致的抽象?
- 类的接口是否让人明白应该如何使用它?
- 类的接口是否足够抽象,是否是个黑盒子?
- 是否从类中去除无关信息?
- 是否可以进一步分解为一组类?
- 修改类时,是否保证其接口的完整性?
封装
- 是否把类的成员的可访问性降到最低?
- 是否完全隐藏了自己的实现细节?
- 类是松耦合的吗?
- 类是否避免了对其使用者做假设?
继承
- 派生类是否遵循LSP原则?
- 派生类是否避免了覆盖,不可覆盖的方法?
- 基类中的数据是否是private的?
- 继承层次是否很浅?
跟实现相关的其它问题
- 类中是否只有大约七个或更少的成员?
- 类调用其它类的子程序降低到最少了吗?
- 类是否在绝对必要时才与其它类协作?
- 所有数据成员是否在构造函数中初始化了?
- 是否在没有论证的情况下使用了浅拷贝,而不是深拷贝?