What is a detailed explanation of the "Subclass only where it makes sense" argument?
From a presentation titled How to Build a Good API and Why It Matters
I am stuck on page 25 of the presentation, which says:
Public classes should not subclass other public classes for ease of implementation
And he gave us examples (Java syntax):
Bad: Properties extends Hashtable
Stack extends Vector
Good: Set extends Collection
But why are these examples good and bad?
source to share
Since Properties
it is not Hashtable
, and they should not be used interchangeably, i.e. you don't want users to use Properties
where they only need Hashtable
. The same goes for Stack
vs Vector
.
Good design should strive for API simplicity. If you are designing Stack
, you should basically only provide methods push
and pop
. Public inheritance from Vector
removes implementation details that the user doesn't need to know. Confusion aside, this means you can never change the implementation! So if tomorrow Vector
becomes obsolete (in my opinion it really is now), you still stick Stack
with who uses it because your customers might expect it. Changing the implementation will break backward compatibility, which is another design goal.
Please note that the above example is not random. Both Vector
and Hashtable
are classes that are considered obsolete (see. Last comments here and here ). These classes have some design flaws and have been replaced by ArrayList
and HashMap
or similar others. This makes classes that inherit from them deprecated as well. If instead of inheritance you use composition, you could easily change Vector
and Hashtable
for their modern counterparts, without affecting any user.
On the other hand, it Set
is Collection
. That is, if some code indicates that it needs some Collection
, the user can provide Set
(or a List
or something else). This gives a lot of flexibility to the API as long as there are no specific requirements for what the collection is supposed to provide (for example, no random access).
source to share
Inheriting from a class is usually seen as implementing an "is-a" relationship.
Is a property set a hash table? No, not at all. The hash table is an implementation detail, not a fundamental property of "Properties". This means that you have to use aggregation, for example:
class Properties {
private HashTable mPropertyTable;
}
The same goes for Stack and Vector. Stack is not a special kind of Vector, so Vector must be a member of an implementation-only stack.
Contrast this with a Set sourced from the collection. Is a set a collection type? Yes, it does, so inheritance makes sense in this case.
source to share
Bloch distinguishes interface inheritance from implementation inheritance.
It is impossible to know from the slide deck what he said at this point in his presentation, but the typical argument for avoiding inheriting one public class from another is that it constantly binds the subclass to the superclass's implementation. For example, Properties
it cannot implement its property store in a form HashMap
or in any other form other than HashTable
.
Another argument is that it ties the classes together too tightly. Modifications that would be useful to the superclass can break the subclass or worsen its behavior.
The third argument is that the subclass inherits methods from the superclass, which may not make sense to it, or might do so in the future if methods are added to the superclass. For example, since Stack
extends Vector
, you can view arbitrary elements below the top and even add, remove, or modify inner elements. This applies to some extent to interface inheritance, but in this case it usually isn't that much.
The first two arguments remain somewhat applicable even if super- and subclasses have an is-a relationship.
source to share