The reason is that if you allow a subclass and do not set restrictions on which methods the subclass can override, updates to the base class that preserve its behavior can still subclass subclasses. In other words, it is unsafe to inherit from a class that is not specifically designed for extension (by assigning certain methods as redefinable and creating all the rest final .) This is called a fragile base class problem - see this document for an example and this document for a more thorough analysis of the problem .
If the base class was not intended to be inherited and is part of a public API (possibly a library), you now have serious problems. You have no control over who subclasses your code, and you cannot find out if the change will be safe for subclasses simply by looking at the base class. The bottom line is that you either develop unlimited inheritance, or completely abandon it.
Note that it is possible to have a finite number of subclasses. This can be achieved using a private constructor and inner classes:
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 { ... } }
Inner classes have access to a private constructor, but top-level classes do not, so you can subclass it only internally. This allows us to express the view that "a Base value may be SubA OR SubB OR SubC." There is no danger here, because Base will usually be empty (you don't actually inherit anything from Base), and all subclasses are under your control.
You might think that this is the smell of code, as you know the types of subclasses. But you must understand that this alternative way of implementing abstractions complements the interfaces. When you have a finite number of subclasses, it is easy to add new functions (handle a fixed number of subclasses), but it is difficult to add new types (every existing method needs to be updated to handle a new subclass). When you use the interface or allow unlimited subclasses, it is easy to add a new type (implement a fixed number of methods), but it is difficult to add new methods (you need to update each class). One of the strengths is another weakness. For a more detailed discussion of this topic, see About Understanding Data Abstraction, Revised . EDIT: My mistake; the document I linked says about another duality (ADTs vs objects / interfaces). Actually, I was thinking about the problem of expression .
There are less serious reasons to avoid inheritance. Suppose you start with class A and then implement a new function in subclass B, and then add another function to subclass C. Then you realize that you need both functions, but you cannot create a subclass BC that extends as B , and C. This can be bitten even if you create for inheritance and prevent the problem of a fragile base class. In addition to the pattern that I showed above, most inheritance applications are best replaced with composition โ for example, using the Strategy Pattern (or just using high-order functions if your language supports it.)
Doval Jan 14 '14 at 15:12 2014-01-14 15:12
source share