《代码大全2》阅读笔记01--Chapter 6 Working Classes

Chapter 6 Working Classes (Page 162 - 197)

6.1 Class Foundations: Abstract Data Types(ADTs)
      类的基础 : 抽象数据类型(ADTs)

          ADT是指一些数据以及对这些数据所进行的操作的集合。
6.2 ADTs and Classes
    Good Class Interfaces
把每个抽象数据类型用他自己的类实现。
类还涉及到继承和多态两个额外的概念。因此,考虑类的另一种方式就是把它看做是抽象数据类型再加上继承和多态两个概念。
同时考虑抽象性和内聚性
一个呈现出很好的抽象的类接口通常也有很高的内聚性。而具有很强内聚性的类往往也会呈现为很好的抽象。

    良好的封装 Good Encapsulation
封装是一个比抽象更强的概念。抽象通过提供一个可以让你忽略实现细节的模型来管理复杂度,而封装则强制阻止你看到细节。
封装的原则:
1. 尽可能的限制类和成员的可访问性(accessibility)
2. 避免把私用的实现细节放入类的接口中。
3. 留意过于紧密的耦合关系(coupling)
      a. 尽可能的限制类和成员的可访问性。
      b. 避免友元类,因为他们之间是紧密耦合的。
      c. 在类中把数据声明成Private而不是Protected,以降低生类和基类之间的耦合的程度。
      d. 避免在类的公开接口中暴露成员数据。
      e. 要对从语义上破坏封装性保持警惕。
      f. 察觉 Demeter法则

6.3 有关设计和实现的问题
 Design and implementation Issues
1. 包含(“有一个……”的关系) Containment
警惕有超过7个数据成员的类
 研究表明,人们在做其他事情时,能够记住的离散项目的个数
是7+-2个。如果一个类中包含有超过7个数据成员时,请考虑要不要把他分解成为几个更小的类。

2. 继承(“是一个……”的关系)Inheritance
继承的概念是说一个类是另一个类的特化(specialization)。
目的,继承能够把共有的元素集中在一个基类中,从而有助于避免在多处出现重复的代码和数据。
     a. 要么使用继承并进行详细说明,要么就不要使用它。因为继承给程序增加了复杂度,因此它是一种危险的技术。
     b. 遵循Liskov替换原则
      除非派生类真的“是一个”更特殊的基类,否则不应该从基类继承。
      总结为:派生类必须能通过基类的接口而被使用,而且使用者无须了解两者之间的差易。
     c. 确保只继承需要继承的部分
     d. 不要“覆盖”一个不可覆盖的成员函数。
      C++和JAVA两种语言都允许“覆盖”那些不可覆盖的成员函数。例如:
     一个成员函数在基类中是私有(Private)的话, 其派生类可以创建一个同名的成员函数。
 这个函数是令人困惑的,因为它看上去似乎应该是多态的,但是事实上并非如此,只是同名而已。
     e. 把
共用的接口,数据及操作放到继承树中尽可能高的位置。
     f. 只有一个实例的类是值得怀疑的
      只需要一个实例,这可能表明设计中把对象和类混为一谈了。
     g. 只有一个派生类的基类也是值得怀疑的。
           每当我看到只有一个派生类的基类时,我就怀疑某个程序员又在进行“提前设计”了----也就是试图去预测未来的需要,而又常常没有真正了解未来到底需要什么。 也就是说,不要创建任何并非绝对必要的继承结构。
     h. 派生后覆盖了某个子程序,但又在其中没做任何的操作,这种情况也值得怀疑。这通常表明基类的设计有错误。
     i. 避免让继承体系过深。
     面向对象的编程方法提供了大量可以用来管理复杂度的技术。然而每种强大的工具都有其危险之处,甚至有些面向对象技术还有增加发杂度的趋势。

     建议把继承层次限制在6层之内。
     j. 尽量使用多态,避免大量的类型检查。
     k. 让所有的数据都是Private(而非Protected)
      “继承会破坏封装。”

多重继承 Multiple Inheritance
     虽然有些专家建议广泛使用多重继承,但是,多重继承的用途主要是定义
     “混合体(Mixins)”,也就是一些能给对象增加一组属性的简单类。
     混合体需要使用多重继承,但只要所有的混合体之间保持完全独立,他们不会导致典型的菱形继承(diamond-inheritance)问题。
     JAVA和VB语言是认可混合体的价值,因为他们允许多重接口继承,但只能继承一个类的实现。
     而C++则同时支持接口和实现的多重继承。
     程序员在决定使用多重继承之前,应该仔细地考虑其他替代方案,并谨慎地评估它可能对系统的复杂度和可理解性产生的影响。

Why Are There So Many Rules for Inheritance?
为什么有这么多关于继承的规则?
总结一下什么时候可以使用继承,何时又该使用包含:
1. 如果多个类共享数据而非行为,应该创建这些类可以包含的共用对象。
2. 如果多个类共享行为而非数据,应该让他们从共同的基类继承而来,并在基类里定义共用的子程序。
3. 如果多个类机共享数据也共享行为,应该让他们从一个共同的基类继承而来,并在基类里定义共用的数据和子程序。
4. 当你想由基类控制接口时,使用继承;当你想自己控制接口时,使用包含。

Member Function and Data
成员函数和数据成员
有效地实现成员函数和数据成员的建议:
1. 让类中子程序的数量尽可能少
2. 禁止隐式地产生你不需要的成员函数和运算符
3. 减少类所调用的不同子程序的数量
4. 对其他子程序的间接调用要尽可能减少
5. 一般来说,应尽量减少类和类之间相互合作的范围
 尽量减少:a. 所实例化的对象的种类。 b. 在被实例化对象上直接调用 不同子程序的数量。c. 调用由其他对象返回的对象的子程序的数量。

Constructors
构造函数
1. 如果可能,应该在所有的构造函数中初始化所有的数据成员。
 在所有的构造函数中初始化所有的数据成员是一个不难做到的防御式编程实践。
2. 用Private构造函数来强制实现单件属性。(singleton property)
 如果想定义一个类,并需要强制规定它只能有唯一一个实例对象的话,可以把该类所有的构造函数都隐藏起来,然后对外提供一个static的GetInstance()
子程序来访问该类的唯一实例。列如:以下Java code:
 public class MaxId
 {
  //constructors and destructors
  private MaxId() {...}
  //public routines
  public static MaxId GetInstance()
  {
   return m_instance;
  }
  //private members
  private static final MaxId m_instance = new MaxId();
  ...
 }
3. 优先采用深层复本(Deep Copies);除非论证可行,才采用浅层复本
(shallow copies)

Reason to Create a Class
创建类的理由
下面列出一些创建类的合理原因:
1. 为现实世界中的对象建模
2. 为抽象的对象建模。
3. 降低复杂度
4. 隔离复杂度
5. 隐藏实现细节
6. 限制变动的影响范围
7. 隐藏全局数据
8. 让参数传递更加顺畅。
9. 建立中心控制点
10. 让代码更易于重用。
11. 为程序族做计划
12. 把相关的操作包装到一起
13. 实现某种特定的重构

Classes to Avoid
应该避免的类
下面就是一些应该避免创建的类:
1. 避免创建万能类(God class)
2. 消除无关紧要的类
3. 避免用动词命名的类
 只有行为而没有数据的类往往不是一个真正的类。
 请考虑把DatabaseInitialization或StringBuilder这样的类 变成其他类的子程序。

6.5 Language-Specific Issue
与具体编程语言相关的问题
例如: 如何在一个派生类中通过覆盖成员函数来实现多态。
在JAVA中,所有的方法默认可以被覆盖,除非加上Final。
在C++中,所有的方法默认不可以被覆盖,除非加上Virtual。

6.6 Beyond Classes: Packages
超越类:包
类是当前程序员们实现模块化(modularity)的最佳方式。


要点总结:Key Point
1. 类的接口应该提供一致的抽象。很多问题都是由于违背该原则而引起的。
2. 类的接口应隐藏一些信息----如某个系统接口,某项设计决策,或一些实现细节。
3. 包含往往比继承更为可取----除非你要对“是一个/is a”的关系建模。
4. 继承是一种有用的工具,但他却会增加复杂度,这有违背软件的首要技术使命
 ----管理复杂度。
5. 类是管理复杂度的首选工具。要在设计类时给予足够的关注,才能实现这一目标。

 

你可能感兴趣的:(classes)