> A common counterexample to OO inheritance is the relationship between a square and a rectangle. Geometrically, a square is a specialisation of a rectangle: every square is a rectangle, not every rectangle is a square. For all s in Squares, s is a Rectangle and width of s is equal to height of s. As a type, this relationship is reversed: you can use a rectangle everywhere you can use a square (by having a rectangle with the same width and height), but you cannot use a square everywhere you can use a rectangle (for example, you can’t give it a different width and height).<p>No, a square is a rectangle and you can do anything to a square you can do to a rectangle, <i>viz</i>.:<p><pre><code> class Rectangle {
#width: number;
#height: number;
get width() {
return this.#width;
}
get height() {
return this.#height;
}
constructor(width: number, height: number) {
this.#width = width;
this.#height = height;
}
}
class Square extends Rectangle {
constructor(sideLength: number) {
super(sideLength,sideLength);
}
}
</code></pre>
Even with “a mutable object that can hold be any Square” and “a mutable object that can be any Rectangle”, substitution still works in the normal direction:<p><pre><code> class MutableRectangle {
#width: number;
#height: number;
get width() {
return this.#width;
}
get height() {
return this.#height;
}
set width(newWidth: number) {
this.#width = newWidth;
}
set height(newHeight: number) {
this.#height;
}
constructor(width: number, height: number) {
this.#width = width;
this.#height = height;
}
}
class MutableSquare extends MutableRectangle {
get sideLength() {
return this.width;
}
set sideLength(newSideLength: number) {
super.width=newSideLength;
super.height=newSideLength;
}
set width(newWidth: number) {
this.sideLength=newWidth;
}
set height(newHeight: number) {
this.sideLength=newHeight;
}
constructor(sideLength: number) {
super(sideLength,sideLength);
}
}
</code></pre>
What gets problematic is when you’ve defined the contract for MySpecialMutableRectangle.setHeight/MySpecialMutableRectangle.setWidth to specify that they do not modify the other dimension, then you’ve imposed a constraint such that you can’t have MySpecialMutableSquare as a subset of MySpecialMutableRectangle <i>unless</i> calling the relevant mutation methods changes the <i>class</i> of the object to MySpecialMutableRectangle; there are OOP languages where this is possible (it is a use of become in Smalltalk, for instance), but its never typesafe (where there is a typeset subset of uses of become in Smalltalk, this is outside of it.)<p>The problem here is with understanding the attributes that define a logical subset relationship, and understanding that mutation to those attributes that doesn’t respect the invariants that defining both the set and the subsets invalidates the relationship. And its somewhat tied to the difference between <i>identity</i> of an ojbect and <i>attributes</i> of an object.<p>> Notice that this is incompatibility between the inheritance directions of the geometric properties and the abstract data type properties of squares and rectangles<p>No, its not. Squares and rectangles are geometric entities, not abstract data types. There are <i>infinite number of conceivable pairs of abstract data types</i> whose data members (or a subset thereof) correspond, respectively, to a square and a rectangle, differing in auxiliary data members and in available operations and their contracts. For <i>certain</i> selections within the infinite set available for ADTs that include data corresponding to a rectangle, the contracts chosen will make it impossible to implement a corresponding ADT corresponding to a square that is a proper subtype.<p>> Smalltalk and many later languages use single inheritance for implementation inheritance, because multiple inheritance is incompatible with the goal of implementation inheritance due to the diamond problem<p>“Incompatible” only if one defines “implementation inheritance” as “a class must automatically, by default, use the implementation of a method from the nearest ancestor in every inheritance path if it does not have its own explicit implementation, and this must always be unique”. There’s other possible approaches to implementation inheritance, including: providing a sort order for resolution when multiple inheritance paths exist and conflict, requiring an explicit local implementation or reference to a specific ancestor implementation if the nearest ancestor in all paths is not unique, etc., etc., etc.<p>> On the other hand, single inheritance is incompatible with ontological inheritance, as a square is both a rectangle and an equilateral polygon.<p>No, single inheritance is incompatible with using inheritance to <i>fully model</i> ontological inheritance, it is not incompatible with ontological inheritance (that is, it is compatible with “all uses of inheritance in the program represent ontological relationships” but not (“all heirarchical ontological relationships are modeled in the inheritance heirarchy”.)<p>I will also note that the three types of inheritance specified here are artificial distinctions. Implementation inheritance is obviously distinct from the other tool; the LSP, and thus ADT inheritance, is a concrete consquence of ontological inheritance. Where they have superficial conflicts (as in the earlier dicussion of squares and rectangles in the source article), it is because the ADTs chosen have features modeling things other than the ontological concepts that form a legitimate heirarchy; its frequently a concept of adding additional domain-specific context on top of an abstract (e.g., mathematical or geometrical) ideal, but continuing to name the class after the ideal and assume that the ontological heirarchy that applies to the abstract concepts applies to the “augmented” ADTs which incorporate them <i>along with other concerns</i>.