我正在阅读 J. Bloch 的 Effective Java,现在我正在阅读继承与组合部分。据我了解,他说继承并不总是好的。
子类脆弱性的一个相关原因是它们的超类 可以在后续版本中获取新方法。假设一个程序 其安全性取决于以下事实:所有元素都插入 某些集合满足某些谓词。这是可以保证的by 对集合进行子类化并重写每个能够实现的方法 添加一个元素以确保之前满足谓词 添加元素。这工作得很好,直到一种新方法能够 插入一个元素被添加到后续的超类 释放。
但是为什么不起作用呢?超类只是
Collection
接口,如果我们添加一个新方法,我们只是一个编译时错误。这永远不会有害...
假设您在某个库 v1.0 中有一个 Collection 超类:
public class MyCollection {
public void add(String s) {
// add to inner array
}
}
您对其进行子类化以便仅接受长度为 5 的字符串:
public class LimitedLengthCollection extends MyCollection {
@Override
public void add(String s) {
if (s.length() == 5) {
super.add(s);
}
}
}
这个类的约定,不变的是它永远不会包含长度不为 5 的 String。
现在该库的2.0版本已经发布,您可以开始使用它了。基类修改为:
public class MyCollection {
public void add(String s) {
// add to inner array
}
public void addMany(String[] s) {
// iterate on each element and add it to inner array
}
}
并且您的子类保持不变。现在你的子类的用户可以做
LimitedLengthCollection c = new LimitedLengthCollection();
c.addMany(new String[] {"a", "b", "c"});
你的子类的契约就这样被打破了。它本来应该只接受长度为 5 的字符串,但现在不再接受了,因为超类中添加了一个附加方法。
问题不在于继承不行。
问题在于,通过继承,开发人员无法强制执行某些行为(例如满足某些谓词的集合的示例)。
当我们创建一个新类时,它很少真的是另一个类的专门类型。更多时候是使用其他类的新东西。
我们很少需要继承,更多时候我们需要创建一个使用其他类来完成某些事情的类。
IS A vs HAS A
你必须问自己:
B 类是 A 类的新子类型,它以不同的方式执行 A 类的相同操作?
或
B 类内部有一个类可以做与 A 打算做什么?
并且知道更多时候正确的答案是后者。
如果我们添加一个新的方法,我们只是一个编译时错误
仅当将 abstract 方法添加到超类/接口时才是正确的。如果添加了非抽象方法,则不重写该新方法是完全有效的。
因为它(通常)会破坏实现 Collection 类的客户端代码。
在此特定示例中,安全性将被破坏,因为恶意用户将能够通过使用在您发布代码后添加的尚未重写的方法来插入项目。
将您的代码基于您无法控制的继承类可能会在将来给您带来麻烦。
实现接口的类不是继承,继承是扩展类(超类)添加方法和属性。 所以 Collection 接口不能是超类。
除此之外,在这两种情况下,实现接口或扩展超类并不妨碍您添加可能绕过某些强制行为的额外方法。