类的设计

抽象数据类型

抽象数据类型可以让你像在现实世界中一样操作实体,而不用关心其底层实现。 ADT的益处

  • 隐藏实现细节
  • 限制改动程序影响的范围
  • 让接口提供更多的信息
  • 让程序的正确性更显而易见,易于检查错误
  • 程序更具自我说明性
  • 无须在程序内部到处传递数据
  • 像现实世界中那样操作实体,不用在底层操作它

注意事项 不要让ADT依赖于存储介质。在命名上须要注意。如操作税率表的RateFile类不如RateTable类的命名方式好,虽然目前是通过文件的方式实现的。

类优秀的设计

  1. 类的接口应该展现一致的抽象层次
  2. 一定要理解类所实现的抽象是什么
  3. 提供成对的服务 (如打开,关闭)
  4. 把不相关的信息移到其它类中
  5. 尽可能让接口可编程,而不是表达语义
  6. 谨防在修改时破坏类的抽象
  7. 不要添加与接口抽象不一致的公用成员
  8. 同时考虑类的抽象性和内聚性

良好的封装

  1. 尽可能地限制类和成员的可访问性
  2. 不要公开暴露成员数据
  3. 避免把私用的实现细节放入类的接口中 (C++应该使用信封类)
  4. 不要对类的使用者做出任何假设
  5. 避免使用友元
  6. 不要因为一个子程序里仅使用了公用子程序,就把它归入公开接口中
  7. 让阅读代码比编写代码更方便
  8. 警惕从语义上破坏封装性 (如在调用B之前要先调用A初始化数据,但是由于开发人员知道B中会去调用A,所以不调用A)
  9. 留意过于紧密的耦合关系

有关设计和实现的问题

包含关系

  • 通过包含来实现有一个的关系
  • 在万不得已情况下才使用private继承来实现有一个关系
  • 数据成员应该控制5~9个

继承关系

  • 用public继承来实现“是一个”的关系
  • 使用继承时必须进行详细说明
  • 遵循LSP原则(派生类完全可以通过基类的接口来使用,而使用者无须了解两者之间的差异)
  • 确保只继承需要继承的部分
  • 把共用的接口,数据和操作尽可能的放在更高的抽象层次上
  • 只有一个实例的继承值得怀疑
  • 只有一个派生类也值得怀疑(提前设计?)
  • 派生类覆盖某个子程序,但在其中不做任何操作,值得怀疑
  • 避免让继承体系过深
  • 尽量使用多态,避免大量的类型检查
  • 让所有数据都是private的

成员函数和数据成员

  1. 让类中的子程序数量尽可能少
  2. 禁止隐示的产生不需要的成员函数和运算符
  3. 对其它类的子程序调用尽可能少
  4. 尽量减少类和类之间的合作范围

    所实例化的类的数量 调用其它类的子程序的数量 调用其它对象返回对象的子程序的数量

构造函数

优化采用深层Copy而不是浅Copy,除非论证浅Copy的可行性。 浅Copy虽然会提升性能,但会加大管理的复杂度,过早代码性能优化是没有意义的,后期可以很容易的把深Copy重构为浅Copy

创建类的原因

每次创建类时,都应该问一下自己创建这个类的原因。

  • 为现实世界中的对象建模
  • 为抽象的对象建模
  • 降低复杂度
  • 隔离复杂度
  • 建立一个控制中心
  • 限制变动的范围
  • 隐藏实现的细节
  • 隐藏全局数据
  • 让参数传递更顺畅
  • 让代码容易重用
  • 把易变的区域封装
  • 把相关操作包装到一起
  • 实现某种特定的重构

核对表

抽象

  • 类是否有一个中心目的,类的命名是否符合这个中心目的?
  • 类的接口是否展现一致的抽象?
  • 类的接口是否让人明白应该如何使用它?
  • 类的接口是否足够抽象,是否是个黑盒子?
  • 是否从类中去除无关信息?
  • 是否可以进一步分解为一组类?
  • 修改类时,是否保证其接口的完整性?

    封装

  • 是否把类的成员的可访问性降到最低?
  • 是否完全隐藏了自己的实现细节?
  • 类是松耦合的吗?
  • 类是否避免了对其使用者做假设?

    继承

  • 派生类是否遵循LSP原则?
  • 派生类是否避免了覆盖,不可覆盖的方法?
  • 基类中的数据是否是private的?
  • 继承层次是否很浅?

    跟实现相关的其它问题

  • 类中是否只有大约七个或更少的成员?
  • 类调用其它类的子程序降低到最少了吗?
  • 类是否在绝对必要时才与其它类协作?
  • 所有数据成员是否在构造函数中初始化了?
  • 是否在没有论证的情况下使用了浅拷贝,而不是深拷贝?

Comments