原因是,如果您允许子类化,并且对子类可以覆盖的方法没有限制,则对保留其行为的基类进行更新仍然可以破坏子类。换句话说,从不是专门设计为扩展的类继承是不安全的(通过将特定方法指定为可覆盖并使所有其他方法final
。)这被称为脆弱基类问题 - 请参阅本文以获取示例,并且本文对这个问题进行了更深入的分析。
如果基类不是为继承而设计的,并且它是公共 API(可能是库)的一部分,那么您现在就遇到了严重的麻烦。您无法控制谁对您的代码进行子类化,并且您无法仅通过查看基类来知道更改对子类是否安全。底线是您要么设计无限继承,要么完全禁止它。
请注意,可能有有限数量的子类。这可以通过私有构造函数和内部类来实现:
class Base {
private Base() {}
(public|private) static final class SubA extends Base { ... }
(public|private) static final class SubB extends Base { ... }
(public|private) static final class SubC extends Base { ... }
}
内部类可以访问私有构造函数,但顶级类没有,因此您只能从内部对其进行子类化。这允许您表达“Base 类型的值可以是 SubA OR SubB OR SubC”的概念。这里没有危险,因为 Base 通常是空的(您并没有真正从 Base 继承任何东西)并且所有子类都在您的控制之下。
您可能会认为这是代码异味,因为您知道子类的类型。但是您应该意识到,这种实现抽象的替代方式是对接口的补充。当子类数量有限时,添加新函数很容易(处理固定数量的子类),但很难添加新类型(每个现有方法都需要更新以处理新子类)。当您使用接口或允许无限子类化时,添加新类型很容易(实现固定数量的方法),但很难添加新方法(您需要更新每个类)。一个人的优点就是另一个人的缺点。有关该主题的更详细讨论,请参阅On Understanding Data Abstraction, Revisited。编辑:我的错; 我链接的论文讨论了不同的二元性(ADT 与对象/接口)。我真正想到的是Expression Problem。
There's less severe reasons to avoid inheritance as well. Suppose you start with class A, then implement some new feature in subclass B, and later on add another feature in subclass C. Then you realize you need both features, but you can't create a subclass BC that extends both B and C. It's possible to get bitten by this even if you design for inheritance and prevent the fragile base class problem. Other than the pattern I showed above, most uses of inheritance are better replaced with composition - for example, using the Strategy Pattern (or simply using high-order functions if your language supports it.)