modify item 18 and item 19 (#42)

* modify item 18

* fix typo

* modify item 19

* 修改第十八条的表述
This commit is contained in:
GungnirLaevatain 2020-01-15 09:45:34 +08:00 committed by Joe
parent 37eed9ae6a
commit 8a83c54a85
2 changed files with 5 additions and 5 deletions

View File

@ -181,7 +181,7 @@ static void walk(Set<Dog> dogs) {
  包装类的缺点很少。 一个警告是包装类不适合在回调框架callback frameworks中使用其中对象将自我引用传递给其他对象以用于后续调用「回调」。 因为一个被包装的对象不知道它外面的包装对象所以它传递一个指向自身的引用this回调时并不记得外面的包装对象。 这被称为 SELF 问题[Lieberman86]。 有些人担心转发方法调用的性能影响,以及包装对象对内存占用。 两者在实践中都没有太大的影响。 编写转发方法有些繁琐,但是只需为每个接口编写一次可重用的转发类,并且提供转发类。 例如,`Guava` 为所有的 `Collection` 接口提供转发类[Guava]。
  只有在子类真的是父类的子类型的情况下,继承才是合适的。 换句话说只有在两个类之间存在「is-a」关系的情况下B 类才能继承 A 类。 如果你试图让 B 类继承 A 类时,问自己这个问题:每个 B 都是 A 吗? 如果你不能如实回答这个问题,那么 B 就不应该继承 A。如果答案是否定的那么 B 通常包含一个 A 的私有实例,并且暴露一个不同的 API A 不是 B 的重要部分 ,只是其实现细节。
  只有在子类真的是父类的子类型的情况下,继承才是合适的。 换句话说只有在两个类之间存在「is-a」关系的情况下B 类才能继承 A 类。 如果你试图让 B 类继承 A 类时,问自己这个问题:每个 B 都是 A 吗? 如果你不能明确的以“是的”来回答这个问题,那么 B 就不应该继承 A。如果答案是否定的那么 B 通常包含一个 A 的私有实例,并且暴露一个不同的 API A 不是 B 的重要部分 ,只是其实现细节。
  在 Java 平台类库中有一些明显的违反这个原则的情况。 例如,`stacks` 实例并不是 `vector` 实例,所以 `Stack` 类不应该继承 `Vector` 类。 同样,一个属性列表不是一个哈希表,所以 `Properties` 不应该继承 `Hashtable` 类。 在这两种情况下,组合方式更可取。
@ -189,7 +189,7 @@ static void walk(Set<Dog> dogs) {
  在决定使用继承来代替组合之前,你应该问自己最后一组问题。对于试图继承的类,它的 API 有没有缺陷呢? 如果有,你是否愿意将这些缺陷传播到你的类的 API 中?继承传播父类的 API 中的任何缺陷,而组合可以让你设计一个隐藏这些缺陷的新 API。
  总之,继承是强大的,但它是有问题的,因为它违反封装。 只有在子类和父类之间存在真正的子类型关系时才适用。 即使如此,如果子类与父类不在同一个包中,并且父类不是为继承而设计的,继承可能会导致脆弱性。 为了避免这种脆弱性,使用合和转发代替继承,特别是如果存在一个合适的接口来实现包装类。 包装类不仅比子类更健壮,而且更强大。
  总之,继承是强大的,但它是有问题的,因为它违反封装。 只有在子类和父类之间存在真正的子类型关系时才适用。 即使如此,如果子类与父类不在同一个包中,并且父类不是为继承而设计的,继承可能会导致脆弱性。 为了避免这种脆弱性,使用合和转发代替继承,特别是如果存在一个合适的接口来实现包装类。 包装类不仅比子类更健壮,而且更强大。

View File

@ -111,16 +111,16 @@ public final class Sub extends Super {
  但是普通的具体类呢? 传统上,它们既不是 `final` 的,也不是为了子类化而设计和文档说明的,但是这种情况是危险的。每次修改这样的类,则继承此类的子类将被破坏。 这不仅仅是一个理论问题。 在修改非 `final` 的具体类的内部之后,接收与子类相关的错误报告并不少见,这些类没有为继承而设计和文档说明。
  **解决这个问题的最好办法是,在没有想要安全地子类化的设计和文档说明的类中禁止子类化。 有两种方法禁止子类化。** 两者中较容易的是声明类为 `final`。 另一种方法是使所有的构造方法都是私有的或包级私有的,并且添加公共静态工厂来代替构造方法。 这个方案在内部提供了使用子类的灵活性,在条目 17 中讨论过。两种方法都是可以接受的。
  **解决这个问题的最佳方法是禁止对在设计上和文档说明中都不支持安全子类化的类进行子类化。** 这有两种方法禁止子类化。 两者中较容易的是声明类为 `final`。 另一种方法是使所有的构造方法都是私有的或包级私有的,并且添加公共静态工厂来代替构造方法。 这个方案在内部提供了使用子类的灵活性,在条目 17 中讨论过。两种方法都是可以接受的。
  这个建议可能有些争议,因为许多程序员已经习惯于继承普通的具体类来增加功能,例如通知和同步等功能,或限制原有类的功能。 如果一个类实现了捕获其本质的一些接口,比如 `Set``List` 或 `Map`,那么不应该为了禁止子类化而感到愧疚。 在条目 18 中描述的包装类模式为增强功能提供了继承的优越选择。
  如果一个具体的类没有实现一个标准的接口,那么你可能会通过禁止继承来给一些程序员带来不便。 如果你觉得你必须允许从这样的类继承,一个合理的方法是确保类从不调用任何可重写的方法,并文档说明这个事实。 换句话说完全消除类的自用self-use的可重写的方法。 这样做,你将创建一个合理安全的子类。 重写一个方法不会影响任何其他方法的行为。
  如果一个具体的类没有实现一个标准的接口,那么你禁止继承可能给一些程序员带来不便。 如果你觉得你必须允许从这样的类继承,一个合理的方法是确保类从不调用任何可重写的方法,并文档说明这个事实。 换句话说完全消除类的自用self-use的可重写的方法。 这样做,你将创建一个合理安全的子类。 重写一个方法不会影响任何其他方法的行为。
  你可以机械地消除类的自我使用的重写方法,而不会改变其行为。 将每个可重写的方法的主体移动到一个私有的“帮助器方法”,并让每个可重写的方法调用其私有的帮助器方法。 然后用直接调用可重写方法的专用帮助器方法来替换每个自用的可重写方法。
  简而言之,专门为了继承而设计类是一件很辛苦的工作。你必须建立文档说明其所有的自用模式,并且一旦建立了文档,在这个类的整个生命周期中都必须遵守。如果没有做到,子类就会依赖父类的实现细节,如果父类的实现发生了变化,它就有可能遭到破坏。为了允许其他人能编写出高效的子类,还你必须导出一个或者多个受保护的方法。除非知道真正需要子类,否则最好通过将类声明为 `final`,或者确保没有可访问的构造器来禁止类被继承。
  简而言之,专门为了继承而设计类是一件很辛苦的工作。你必须建立文档说明其所有的自用模式,并且一旦建立了文档,在这个类的整个生命周期中都必须遵守。如果没有做到,子类就会依赖父类的实现细节,如果父类的实现发生了变化,它就有可能遭到破坏。为了允许其他人能编写出高效的子类,你还必须暴露一个或者多个受保护的方法。除非意识到真的需要子类,否则最好通过将类声明为 `final`,或者确保没有可访问的构造器来禁止类被继承。