Learning Inheritance, Substitution vs Extension
We are going to look at inheritance hierarchy. If we take a classic approach, as shown below we would see the methods that have been defined in the base class overridden in the derived classes. Inheritance guarantees that any derived class will have the interface of the base class. If you follow this diagram below, derived classes will also have no more than the base-class interface.
This can be thought of as pure substitution, because derived class objects can be perfectly substituted for the base class, and you never need to know any extra information about the subclasses when you’re using them. Everything is basically handled through polymorphism. When you see this from the perspective that all you need to do is upcast from the derived class and never look back to see what exact type of object you’re dealing with this way, it seems like a pure is-a relationship is the only sensible way to do things, and any other design indicates muddled thinking and is by definition broken. This is considered a trap. If you think deeply into this you will discover that a better approach is to extend the interface is a better solution to this type of problem. Rather than having a is-a relationship, one can define a “is-like-a” relationship, because the derived class is like the base class. It has the same fundamental interface—but it has other features that require additional methods to implement:
While this is a preferred approach (in most situations), it also has its drawbacks. The extended part of the interface in the derived class is not available from the base class, so once you upcast, you cannot call the new methods: If you have no need for upcasting in this case, it won’t bother you, but often you’ll get into a situation in which you need to rediscover the exact type of the object so you can access the extended methods of that type.