第六章:可以工作的类

抽象数据类型(ADTs:Abstract Data Types)
指一些数据,以及对这些数据所进行的操作的集合。
使用ADT可以隐藏实现细节、让接口能提供更多信息,改动不会影响到整个程序,更容易提高性能,程序具有自我说明性...
  • 把常见的底层数据类型创建为ADT并使用这些ADT,而不再使用底层数据类型。
  • 把像文件这样的常用对象当做ADT。
  • 不要让ADT依赖于存储介质:比如在命名的时候,ADT是不关心数据是存在mysql还是mongo或者别的地方的。
良好的类接口
好的抽象:
  • 如果类的接口不能展现出一种一致的抽象,那么它的内聚性就很弱。应该把这些子程序重新组织到几个只能更专一的类里去。
  • 类的接口应该展现一致的抽象层次。
  • 一定要理解类所实现的抽象是什么。
  • 把不相干的信息转移到其他类中。
  • 尽可能让接口可编程而不是表达语意。
  • 每一个接口都有可编程部分和一个语义部分组成,语义应该通过注释说明,但要尽可能的不让接口依赖于这些说明。
  • 谨防在修改时破坏接口的抽象。
  • 同时考虑抽象性和内聚性。
良好的封装
  • 尽可能的限制类和成员的可访问性
  • 不要公开暴露成员数据,暴露成员数据会破坏封装性,从而限制你对这个抽象的控制能力。
  • 避免把私用的实现细节放入类的接口中。把类A的实现细节放到类AA中,类AA只对类A可见。而对使用类A的代码来说是不可见的。如果你的项目已经有大量的代码没有隐藏实现细节,那么你就应该顶住诱惑,不要到类A接口的私用部分去寻找关于实现细节的线索。对内部的用private。
  • 不要对类的使用者做出任何假设(人与人之间没有信任哈哈哈)
  • 不要因为一个子程序里仅使用公用子程序就把它归入公开接口
  • 让阅读代码比编写代码更方便
  • 要格外警惕从语义上破坏封装性,调用方代码只能依赖于类的公开接口,而不能依赖于类的私用实现。每当你发现自己是依赖查看类内部的实现来得知如何使用这个类时,你就不是在针对接口编程了,而是在透过接口针对内部实现编程了。这样封装性就破坏了,而封装性一旦破坏,抽象能力也快遭殃了。
  • 留意过于紧密的耦合关系
  • 避免友元类,因为它们之间是紧密耦合的
  • 在基类中把数据声明为private而不是protected,以避免派生类和基类之间的耦合
  • 避免在类的公开接口中暴露成员数据
对于继承,遵循Liskov替换原则:
除非派生类(子类)真的是一个更特殊的基类,否则不应该从基类继承。派生类必须能通过基类的接口而被使用,且使用者无需了解两者之间的差异。
把公用的接口、数据以及操作放到继承树中尽可能高的位置。
只有一个实例的类是值得怀疑的。可以考虑是否能用数据差异而不是新的类来表达。(单例模式是一个特例)
只有一个派生类的基类也值得怀疑。这种情况可能是程序员在进行“提前设计”,但是却常常没有了解到未来到底需要什么。应该要让眼下的代码更清晰,简单。不要创建任何并非绝对必要的继承结构。
派生后覆盖了某个子程序,但在其中没有进行任何的操作,这也值得怀疑。
避免让继承体系过深
尽量使用多态,避免大量的类型检查,频繁重复出现case语句有时实在暗示,采用继承可能是种更好地设计选择。
让所有数据都是private:继承会破坏封装
成员函数和数据成员
让类中子程序的数量尽可能少
减少类所调用的不同子程序的数量
对其他类的子程序的间接调用要尽可能的少:
Demeter法则:A对象可以任意调用它自己的子程序,如果A对象创建了一个B对象,A对象也可以调用B对象的任何公用子程序,但是A对象应该避免再调用由B对象所提供的对象中的子程序。
一般来说,应尽量减少类和类之间相互合作的范围,尽量让下面这几个数字最小
  • 所实例化的对象的种类
  • 在被实例化对象上直接调用的不同子程序的数量
  • 调用由其他对象返回的对象的子程序的数量
构造函数
如果可能,应该在构造函数中初始化所有的数据成员
适时地使用单例模式
优先采用深层复本,除非论证可行,才采用浅层复本。深层复本是一个全新的对象,而浅层复本往往只是指向对象的引用。因为为了不确定的性能而增加复杂度是不妥的。
应该避免的类
避免创建万能类
消除无关紧要的类
避免用动词命名的类:只有行为而没有数据的类往往不是一个真正的类
类的质量
抽象
是否把程序中的类都看做是抽象数据类型了?
类是否有一个中心目的?
类的命名是否恰当?
类的接口是否展现了一致的抽象?
类的接口是否易于阅读?
类的接口是否足够抽象?你能把类看做黑盒子吗?
类提供的服务是否足够完整,能让其他类无须动用其内部的数据?
是否已从类中移除无关信息?
是否考虑过把类进一步分解为组件类?
在修改类时是否维持了接口的完整性?
封装
是否把类的成员的可访问性降到最小?
是否避免暴露类中的数据成员?
类是否已尽可能的对其他类隐藏了自己的实现细节?
类是否避免对其调用者进行假设?
类是否不依赖于其他类?它是松散耦合的吗?
继承
继承是否遵循Liskov替换原则?
类的文档是否记述了其继承策略?
派生类是否避免覆盖不可覆盖的方法?
是否把公用的接口,数据,和行为都放到尽可能高的继承层次中了?
继承层次是否很浅?
基类中的所有数据是否都被定义为private而非protected了?
其他
类中是否只有7个或更少的数据成员?
是否把类直接或间接调用其他类的子程序的数量减少到最少了?
类是否只在绝对必要时才与其他类相互协作?
是否在构造函数中初始化了所有数据成员?
是否优先使用深层复本?