TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Why inheritance never made any sense: 3 different inheritances

4 pointsby pcr910303almost 4 years ago

3 comments

linkddalmost 4 years ago
&gt; 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.<p>Well yes and no.<p>A square and a rectangle are two kind of objects that have some common properties.<p>In Math, for an object to belong to a set, it must have the properties described by the set.<p><pre><code> - Squares is the set of polygons with 4 sides of the same length and 4 right angles. - Rectangles is the set of polygons with 4 sides and 4 right angles. - Diamonds is the set of polygons with 4 sides of the same length. </code></pre> So yes, it&#x27;s difficult to represent this relationship with inheritance. But no, in OOP, you should not consider those properties as data on a class, but more like a trait&#x2F;interface.<p>Your functions should take as input shapes implementing the expected traits&#x2F;interfaces.<p>Then what is a shape? It&#x27;s a collection of points. But a point can be 2D, 3D, n-D. Here you would still not use inheritance but generics.<p>Usually, I only use inheritance to specialize configuration, for example:<p><pre><code> class Foo { constructor(options) { ... } } class Bar extends Foo { constructor() { super({ some: &#x27;options&#x27; }) } }</code></pre>
aliasElialmost 4 years ago
In almost all programs inheritance is for re-using code, so it is implementation inheritance. The big problem with that approach is that in most cases there is no contract for sub-classes, the properties that a sub-class is required to have. This can lead to very messy class hierarchies.<p>Also note that a lot of problems disappear when objects are immutable.
dragonwriteralmost 4 years ago
&gt; 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&#x2F;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>&gt; 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>&gt; 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>&gt; 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>.
评论 #27691994 未加载