There are three types of inheritance.
- Ontological inheritance indicates specialization: this thing is a specific kind of that thing (a soccer ball is a sphere and has such and such a radius).
- Inheriting an abstract data type indicates substitution: this piece has the same properties as that piece and such- and -such behavior (this is the Barbara Liskov substitution principle).
- Inheritance of an implementation is related to code sharing: this thing takes some of the properties of that thing and redefines or complements them in this way. Inheritance in my article "On Inheritance" of just such and only this type.
These are three different and often conflicting relationships. Require any or all of them does not present any difficulties. But the requirement for
one mechanism to support two or more of them means running into problems.
Often, for inheritance in OOP, a counterexample of the relationship between a square and a rectangle is given. Geometrically square is
a rectangle specialization: all squares are rectangles, but not all rectangles are squares. All
s in the "Square" class are rectangles of
s whose length is equal to the width. But in the type hierarchy, this relationship is the opposite: you can use a rectangle everywhere where a square is used (specifying a rectangle with the same width and height), but you cannot use a square everywhere where a rectangle is used (for example, you cannot change the length and width).
Notice that there is an incompatibility between the direction of inheritance of the
geometric properties and the
properties of the abstract data type of squares and rectangles. These two dimensions are completely unrelated to each other in any software implementation. We have not yet said anything about the inheritance of the implementation, so we did not even consider writing the program.
')
Smalltalk and many later languages ​​use simple inheritance to inherit an implementation because multiple inheritance is
incompatible with it because of the diamond problem (types provide a
reliable way to declare incompatibility, leaving the solution to the problem as an exercise for the reader). On the other hand, simple inheritance is
incompatible with ontological inheritance, since a square is both a rectangle and an equilateral polygon.
The Smalltalk blue book describes inheritance
solely in terms of implementation inheritance:
"The subclass defines that all its instances will, with the exception of the clearly indicated differences, be the same as the instances of another class called its superclass."
Note the missing part: it is not mentioned that an instance of a subclass should be able to replace an instance of a superclass everywhere in the program; it is not mentioned that an instance of a subclass must satisfy all conceptual tests for an instance of its superclass.
Inheritance has never been a problem: the problem is in trying to use the
same tree for
three different concepts .
“Prefer structure instead of inheritance” is essentially to abandon implementation inheritance. We can’t figure out how to make it work, so let's get rid of it altogether: let's do sharing through delegation, not subclasses.
Eiffel and separate ordered approaches to using languages ​​like Java strengthen the “inheritance is the creation of subtypes” relationship, weakening the “inheritance is reuse” relationship (if the same method appears twice in unrelated parts of the tree, you will have to live with it in order to preserve the properties each subclass is a subtype of its parent). This is normal if you are
also not trying
to model the problem area using the inheritance tree. Normally, OOP literature recommends doing this when it comes to problem-oriented design.
Types strengthen the “inheritance is specialization” relationship by weakening the “inheritance is reuse” relationship (if both supercategories produce the same property of an instance, then none of them are inherited and you must prescribe it yourself). This is normal if you don’t
also try
to treat subclasses as the covariant subtypes of your superclasses, but usually the OOP literature recommends doing so, mentioning the principle of Barbara Liskov's substitution and that the type in the signature of the method means this type
or any subclass .
I believe that the following
should be written in literature: “these are the three types of inheritance, focus on one of them”. I also believe that
languages should support this (obviously, Smalltalk, Ruby and their friends
support this by not having any type restrictions).
- If I use inheritance to share code, I should not assume that my subclasses are also subtypes at the same time.
- If I use subtypes to strengthen interface contracts, I must not only be allowed to mark a class anywhere in the tree as a subtype of another class anywhere in the tree, but must be required to do so. Again, one should not assume that my subclasses are also subtypes at the same time.
- If I need to specify conceptual specialization through classes, this should also not imply the observance of the inheritance tree. I must not only be allowed to mark a class anywhere in the tree as a subset of another class anywhere in the tree, but must be required to do so. Again, one should not assume that my subclasses are specializations at the same time.
Your distribution area model is not an object model. This is not an abstract data type model. And the object model is not a model of an abstract data type.
Now inheritance is simple again.