dockerfile/examples/omnivore/api/readabilityjs/test/test-pages/thevaluable.dev/distiller.html

423 lines
55 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div><picture><source srcset="https://thevaluable.dev/images/2021/inheritance/inheritance_evil.webp" type="image/webp" src="https://thevaluable.dev/images/2021/inheritance/inheritance_evil.webp"/><img src="https://thevaluable.dev/images/2021/inheritance/inheritance_evil.jpg" alt="Is inheritance that evil?"/></picture><p>
“You used inheritance in your code! Are you crazy? Its forbidden! Its clearly written in the Laws and Mantras of Good Software Practice Everyone Must Follow™ hanged in the toilets!”
</p><p>
Your thoughts about the nice Youtube video full of cute dogs you saw yesterday stop abruptly. Anxious, you look on your right: Dave, your colleague developer, is yelling at Davina, your desk neighbor. All three of you are working for the fantastic company MegaCorpMoneyMaker, the famous e-commerce which can sell ice to penguins.
</p><p>
“Thats true, Dave, youre right. I used inheritance”, begins Davina. “No need to scream. You could have written it during the code review.”
</p><p>
Dave, disarmed by her calm and her honesty, replies: “I… thats true but… well… I wanted to make an example! We should ban the Demon of Inheritance from the surface of Earth. It will destroy our codebase, our companies, our jobs, and our lives.” Hes now addressing the whole open-space. “Inheritance is evil! It has always been, and it will always be. Composition will save us all!”.
</p><p>
Inheritance is considered as a “pillar of OOP” in many articles, books, and other resources on software development. But, at the same time, many developers, like Dave, will recommend not using it in every possible context. Why is that?
</p><p>
Well try to answer this question throughout this article. In particular, well see:
</p><ul><li>The properties of this concept we call inheritance.</li><li>A brief history of inheritance to understand where it comes from.</li><li>How powerful single inheritance can be.</li><li>Composition versus single inheritance.</li><li>The legacy of inheritance.</li><li>Concrete use of inheritance.</li><li>How modern languages (Rust and Golang) implement inheritance.</li></ul><p>
The few examples of this article are written in PHP. Dont worry if you dont know it, its very easy to understand (when you dont do complicated stuff with it). I dont follow the Perfect Formatting the PHP Grandmasters follow, so dont feel sad about that.
</p><p>
Dave and Davina are ready. Secure yourself and lets go!
</p><h2>
Whats Inheritance?
</h2><p>
After Dave finishes his speech, Davina begins to explain her point of view.
</p><p>
“I dont think inheritance is bad in every situation.”
</p><p>
She pauses, considering. “First, To be sure we understand each other, lets decompose the concept of inheritance. We might discover some misconceptions and learn from each other.”
</p><p>
“Misconceptions…” repeat Dave, doubtful. “Its perfectly clear for me, but go ahead. Lets see if you understand it”.
</p><h3>
Defining Inheritance
</h3><p>
Davina remembers an interesting definition of inheritance encapsulating some core ideas:
</p><blockquote><p>
A class may inherit - use by default - the fields and methods of its superclass. Inheritance is transitive, so a class may inherit from another class which inherits from another class, and so on, up to a base class (typically Object, possibly implicit/absent). Subclasses may override some methods and/or fields to alter the default behavior.
</p></blockquote><p>
Lets decompose this definition:
</p><ul><li><em>superclass</em>: a class which has at least one subclass.</li><li><em>subclass</em>: a class which is a descendant of at least one superclass.<ul><li>It can <em>add</em> new methods or properties, or <em>use</em> methods or properties of the superclass.</li><li>It can modify (or <em>override</em>) some methods or properties of the superclass.</li></ul></li><li><em>base class</em> : a superclass which is not a subclass.</li></ul><p>
In many common programming languages, inheritance has also these two properties you can use together:
</p><ul><li><em>Multilevel inheritance</em>: a subclass can be a superclass of other subclass(es).</li><li><em>Hierarchical inheritance</em>: a superclass can have more than one subclass.</li></ul><p>
Finally, inheritance systems can have one of the following properties but not both:
</p><ul><li><em>Single inheritance</em>: a subclass can only have one superclass.</li><li><em>Multiple inheritance</em>: a subclass can have more than one superclass.</li></ul><p>
At that point, both Davina and Dave agree to speak mostly about single inheritance. Most programming languages wont allow you to use multiple inheritance anyway. More on that later.
</p><p>
Since inheritance is a <em>hierarchy</em> of superclasses and subclasses, its easier to show them than to describe them. Davina draws with the precision of a beaver and the eye of an eagle the following:
</p><picture><source srcset="https://thevaluable.dev/images/2021/inheritance/basics.webp" type="image/webp" src="https://thevaluable.dev/images/2021/inheritance/basics.webp"/><img src="https://thevaluable.dev/images/2021/inheritance/basics.png" alt="Single, multiple, hierarchical, and multilevel inheritance"/></picture><h3>
Inheritance for Code Reuse
</h3><p>
“What can we already see with what we have here?” asks Davina.
</p><p>
You answer this one: “We can see that a given subclass inherit all the properties and behaviors of <em>all of its superclasses</em>. It means that the leaves of our tree (the superclasses without subclasses) concentrate all the behaviors of all their parents”.
</p><p>
Imagine if humanity could conceive children with all the knowledge of each of their ancestors. How smart would they be? Well, maybe not as much as you think. The processing power of our brain is limited, thats why we have difficulty to put in our head all the details of a complex codebase. As a result, its likely that our children would be lost in an ocean of knowledge.
</p><p>
Similarly, the more levels of hierarchy you have in your inheritance tree, the more <a href="https://thevaluable.dev/kiss-principle-explained/" target="_blank" rel="noopener">complex</a> the class inheriting from all this knowledge will be. We will have difficulties to think and <em>reason</em> about them if we need to maintain or modify them.
</p><h3>
Inheritance for Specialization
</h3><p>
For now, we only talked about inheritance as a way to inherit the implementation of superclasses in subclasses. Thats not all: many programming languages allow you to <em>substitute</em> a superclass by one of its subclass thanks to <strong>polymorphism</strong>. The subclasses can be seen as <em>specialization</em> of their superclasses.
</p><p>
This is a very important piece of the inheritance puzzle were lying here. Lets take this exciting example:
</p><pre><code><span>&lt;?php</span> <span>declare</span><span>(</span><span>strict_types</span><span>=</span><span>1</span><span>);</span>
<span>class</span> <span>Parser</span>
<span>{</span>
<span>public</span> <span>function</span> <span>count</span><span>(</span><span>string</span> <span>$filepath</span><span>)</span> <span>{</span>
<span>printf</span><span>(</span><span>&#34;I&#39;m counting lines of %s pretty hard!&#34;</span><span>,</span> <span>$filepath</span><span>);</span>
<span>}</span>
<span>}</span>
<span>class</span> <span>JSONParser</span> <span>extends</span> <span>Parser</span> <span>{</span>
<span>public</span> <span>function</span> <span>parse</span><span>(</span><span>string</span> <span>$filepath</span><span>)</span> <span>{</span>
<span>printf</span><span>(</span><span>&#34;I&#39;m parsing some JSON file %s pretty hard!&#34;</span><span>,</span> <span>$filepath</span><span>);</span>
<span>}</span>
<span>}</span>
<span>class</span> <span>Shipment</span>
<span>{</span>
<span>private</span> <span>Parser</span> <span>$parser</span><span>;</span>
<span>public</span> <span>function</span> <span>__construct</span><span>(</span><span>Parser</span> <span>$parser</span><span>)</span> <span>{</span>
<span>$this</span><span>-&gt;</span><span>parser</span> <span>=</span> <span>$parser</span><span>;</span>
<span>}</span>
<span>public</span> <span>function</span> <span>count</span><span>(</span><span>string</span> <span>$filepath</span><span>)</span> <span>{</span>
<span>$this</span><span>-&gt;</span><span>parser</span><span>-&gt;</span><span>count</span><span>(</span><span>$filepath</span><span>);</span>
<span>}</span>
<span>}</span>
<span>$shipment</span> <span>=</span> <span>new</span> <span>Shipment</span><span>(</span><span>new</span> <span>JSONParser</span><span>());</span>
<span>$shipment</span><span>-&gt;</span><span>count</span><span>(</span><span>&#34;/my/superb/shipment.json&#34;</span><span>);</span>
</code></pre><p>
Whats happening here?
</p><ol><li>We feed to the object <code>Shipment</code> a new <code>JSONParser</code>.</li><li>Even if <code>Shipment</code> expect an object of <a href="https://thevaluable.dev/type-system-explained/">type</a> <code>Parser</code>, you can give <code>JSONParser</code> instead because its a subclass of <code>Parser</code>. Thats good old polymorphism here!</li><li>If you run this code, the surprising output “Im counting lines of /my/superb/shipment.json pretty hard!” is displayed before your amazed eyes. I know, it feels like Christmas.</li></ol><p>
Dave, who begins to get bored, shoot: “I know whats next. Youll speak about the superclass <code>Animal</code> and its subclasses, <code>Dog</code> and <code>Platypus</code>. Im not stupid! Im a Senior Web Developer for 18.3 years now! The CTO told me that I was a Ninja of the Crown this morning and…”.
</p><p>
With a gracious and meaningful movement of her left hand, Davina stop Dave in his enthusiastic show-off. Her eyes light up and her calm but determined voice begins to fill the entire open space. Youre still following the conversation, as well as other colleagues who began, curious, to gather around your desks. The tension begins to rise.
</p><p>
“Not at all”, begins Davina. “OOP was never meant to represent objects or living creatures surrounding our daily life, like a <a href="https://www.atlantic.net/vps-hosting/how-to-object-oriented-programming-constructors-inheritance/" target="_blank" rel="noopener">dog</a>, a <a href="https://www.educative.io/blog/java-inheritance-tutorial" target="_blank" rel="noopener">car</a>, or a <a href="https://stackify.com/oop-concept-inheritance/" target="_blank" rel="noopener">coffee machine</a>. It was invented to solve specific problems linked to software development. In particular, it was designed because we cant <em>reason</em> about complex systems with our limited brainpower. Inheritance is part of this process of problem solving, and we should analyse it in this context.”
</p><p>
She continues. “How many times did you create a <code>Cat</code> class, or a <code>Dog</code> class? You didnt, not even once. You implemented abstract concepts, like a login or a parser, which have nothing to do with these “real-life” examples. Even if some of your classes are loosely related to real life objects, like a <code>Shipment</code> class, you shouldnt even use inheritance for those, as well see later.”
</p><blockquote><p>
… you should be wary of attaching too much importance to the notion that object-oriented systems are directly deduced from the “real world”.
</p></blockquote><p>
The open space is now silent. Everybody begins to realize the lies which was taught to them all these years.
</p><p>
Davina goes on. “These real-life examples confuse you: you have the impress that you understand inheritance because animals and dogs are familiar. Its only a mere illusion. Inheritance can bring a lot of complexity.”
</p><p>
Dave shrugs. Suddenly, Davina stands up, addressing what is now her audience. “Lets see where inheritance come from and why it was invented. Ill tell you now The Story of Inheritance.”.
</p><p>
You see by the window a lightning streaking across the sky. A storm is coming.
</p><h2>
A Brief History of Inheritance
</h2><picture><source srcset="https://thevaluable.dev/images/2021/inheritance/history_inheritance.webp" type="image/webp" src="https://thevaluable.dev/images/2021/inheritance/history_inheritance.webp"/><img src="https://thevaluable.dev/images/2021/inheritance/history_inheritance.jpg" alt="Once upon a time in the land of inheritance"/></picture><p>
Inheritance has been a hot subject since the creation of the OOP paradigm itself. The first programming languages implementing it was <a href="https://en.wikipedia.org/wiki/Simula" target="_blank" rel="noopener">Simula</a> in the 60s. Simula created also most of the concepts we take for granted in OOP, like classes and objects.
</p><p>
The next big step for OOP and inheritance was Smalltalk, a programming language created by Alan Kay and his team at Xerox Park. Its interesting to note that the first implementation of Smalltalk didnt include inheritance. From Alan Kay himself:
</p><blockquote><p>
I didnt like the way Simula I or Simula 67 did inheritance (though I thought Nygaard and Dahl were just tremendous thinkers and designers). So I decided to leave out inheritance as a built-in feature until I understood it better.
</p></blockquote><p>
Dan Ingalls was part of Alan Kay team and ended up implementing five generations of Smalltalk environments. He liked inheritance, and implemented it in every version of Smalltalk following the first one. Its where the disagreement with inheritance began in Software Development; this debate continues today.
</p><p>
What problem Dan Ingalls tried to solve with inheritance? Code reuse. He wanted a mechanism which could help programmers not repeating the same code in different classes. He wanted a system making the knowledge codified in a codebase more <em>general</em>.
</p><p>
Another language designer was heavily influenced by Simula: Bjarne Stroustrup, who created “C with classes”. The goal was to design and reason about complex system more easily. The first implementations of the language were copying many ideas from Simula, including inheritance, while being different from Smalltalk.
</p><p>
“C with classes” became C++. As time passed, the language gained a lot of traction: more and more programmers were using it. Smalltalk, after a huge success, began its descent into the Pit of Forgotten Languages. At the end of the 80s, C++ was the only language implementing multiple inheritance, something many believed impossible to achieve.
</p><p>
But C++ wasnt the only attempt to extend C with classes. Apple Computer had its own version called Objective-C. The language was extended by Steves Job team at NeXT at the beginning of the 90s, and, among other things, they implemented a construct sharing similar properties with single and multiple inheritance. Similar, but not identical: only the <em>interface</em> was inherited, not the implementation. They called this new construct a <em>protocol</em>.
</p><p>
Java, in the middle of the 90s, implemented exactly the same thing, with a different name: <em>interface</em>. I hate this name, its too easy to confuse this “interface” with the more general idea of interface (ways from the outside of a construct to act on its inside). Thats why I call this Java “interface” the <em>interface construct</em> in my articles.
</p><p>
Davina pauses, drink a bit of water, and adds: “When you read in a random tutorial that a class <code>Dog</code> and a class <code>Platypus</code> inherit from a class <code>Animal</code>, do they speak about code reuse?”
</p><p>
You begin to imagine the “code” an <code>Animal</code> could have. Are we in the Matrix?
</p><p>
“Not at all. You speak about specialization: a <code>Cat</code> is a specialized form of <code>Animal</code>, although in this context it doesnt really make sense either. These examples are just lame. Anyway, inheritance is a concept which can bring many powerful features, and thats its main problem, as well see below. Thats why it was discussed for so long and the concept was ultimately dumbed down”.
</p><h2>
How Powerful Is Single Inheritance?
</h2><picture><source srcset="https://thevaluable.dev/images/2021/inheritance/powerful_inheritance.webp" type="image/webp" src="https://thevaluable.dev/images/2021/inheritance/powerful_inheritance.webp"/><img src="https://thevaluable.dev/images/2021/inheritance/powerful_inheritance.jpg" alt="Inheritance is as powerful as Hulk!"/></picture><p>
To understand the drawbacks of single inheritance your colleagues, friends, and dogs are complaining about, lets decompose first what we can precisely do with <em>single inheritance of implementation</em>:
</p><ol><li>You can <em>add</em> some behavior in a subclass.</li><li>You can <em>override</em> (modify) the behavior of any superclass in a subclass.</li></ol><p>
Lets see what the possible benefits and drawbacks of these two approaches.
</p><h3>
Adding Behavior In a Subclass
</h3><p>
Heres a slightly modified version of our legendary parser:
</p><pre><code><span>&lt;?php</span> <span>declare</span><span>(</span><span>strict_types</span><span>=</span><span>1</span><span>);</span>
<span>class</span> <span>Parser</span>
<span>{</span>
<span>public</span> <span>function</span> <span>count</span><span>(</span><span>string</span> <span>$filepath</span><span>)</span> <span>{</span>
<span>printf</span><span>(</span><span>&#34;I&#39;m counting lines of %s pretty hard!&#34;</span><span>,</span> <span>$filepath</span><span>);</span>
<span>}</span>
<span>}</span>
<span>class</span> <span>JSONParser</span> <span>extends</span> <span>Parser</span> <span>{</span>
<span>public</span> <span>function</span> <span>parse</span><span>(</span><span>string</span> <span>$filepath</span><span>)</span> <span>{</span>
<span>printf</span><span>(</span><span>&#34;I&#39;m parsing some JSON file %s pretty hard!&#34;</span><span>,</span> <span>$filepath</span><span>);</span>
<span>}</span>
<span>}</span>
<span>class</span> <span>Shipment</span>
<span>{</span>
<span>private</span> <span>JSONParser</span> <span>$parser</span><span>;</span>
<span>public</span> <span>function</span> <span>__construct</span><span>(</span><span>JSONParser</span> <span>$parser</span><span>)</span> <span>{</span>
<span>$this</span><span>-&gt;</span><span>parser</span> <span>=</span> <span>$parser</span><span>;</span>
<span>}</span>
<span>public</span> <span>function</span> <span>count</span><span>(</span><span>string</span> <span>$filepath</span><span>)</span> <span>{</span>
<span>$this</span><span>-&gt;</span><span>parser</span><span>-&gt;</span><span>count</span><span>(</span><span>$filepath</span><span>);</span>
<span>}</span>
<span>public</span> <span>function</span> <span>import</span><span>(</span><span>string</span> <span>$filepath</span><span>)</span> <span>{</span>
<span>$this</span><span>-&gt;</span><span>parser</span><span>-&gt;</span><span>parse</span><span>(</span><span>$filepath</span><span>);</span>
<span>}</span>
<span>}</span>
<span>$shipment</span> <span>=</span> <span>new</span> <span>Shipment</span><span>(</span><span>new</span> <span>JSONParser</span><span>());</span>
<span>$shipment</span><span>-&gt;</span><span>count</span><span>(</span><span>&#34;/my/superb/shipment.json&#34;</span><span>);</span>
<span>$shipment</span><span>-&gt;</span><span>import</span><span>(</span><span>&#34;/my/superb/shipment.json&#34;</span><span>);</span>
</code></pre><p>
The class <code>JSONParser</code> inherit the implementation of <code>Parser</code> and add its own method <code>parse</code>.
</p><p>
How our system supports changes in this example? If we modify the behavior of the method <code>count</code> from <code>Parser</code>, every subclass inheriting from <code>Parser</code> will get the change too. In that sense, inheritance breaks encapsulation between the superclasses and their subclasses.
</p><blockquote><p>
In languages with inheritance, a data abstraction implementation (i.e., a class) has two kinds of users. There are the “outsiders” who simply use the objects by calling the operations. But in addition there are the “insiders.” These are the subclasses, which are typically permitted to violate encapsulation.
</p></blockquote><p>
Lets imagine that <code>Parser</code> has two subclasses, and these subclasses have two more subclasses. You end up with a hierarchy on 3 levels. It doesnt seem that much of a stretch, but you still end up with seven classes in total. If you modify the base class <code>Parser</code>, six other classes will be affected!
</p><p>
We didnt create a huge inheritance tree here, but changing a superclass has a rippling effect in the whole hierarchy.
</p><blockquote><p>
Hierarchical systems seem to have the property that something considered as an undivided entity on one level, is considered as a composite object on the next lower level of greater detail.
</p></blockquote><p>
Dont get me wrong: it can be beneficial if you <em>want</em> that each change of a superclass affects every subclass at every level below. Actually, its the main reason why inheritance was invented at the first place: being able to make the code more general and reusing it easily. But you need to be sure that your classes are very <a href="https://thevaluable.dev/single-responsibility-principle-revisited/#cohesion" target="_blank" rel="noopener">cohesive</a>, that is, the change of any superclass <em>needs</em> to affect every subclass.
</p><p>
If your classes are not cohesive, youll have all the drawbacks of tight coupled classes in your face: nobody will know if changing a superclass will either cover them with glory and fame or crash the entire system. Your codebase, while growing in complexity, will become impossible to reason about, because you dont have enough brainpower to build in your head an accurate mental model of all the effects of a change. In short, youll end up with one of the problem the OOP paradigm tried to solve at the first place.
</p><h3>
Overriding Behavior in a Subclass
</h3><p>
Lets continue further by adding a new element in our inheritance soup: overriding. Heres another simple example:
</p><pre><code><span>&lt;?php</span> <span>declare</span><span>(</span><span>strict_types</span><span>=</span><span>1</span><span>);</span>
<span>class</span> <span>Parser</span>
<span>{</span>
<span>public</span> <span>function</span> <span>count</span><span>(</span><span>string</span> <span>$filepath</span><span>)</span> <span>{</span>
<span>printf</span><span>(</span><span>&#34;I&#39;m counting lines of %s pretty hard!</span><span>\n</span><span>&#34;</span><span>,</span> <span>$filepath</span><span>);</span>
<span>}</span>
<span>}</span>
<span>class</span> <span>JSONParser</span> <span>extends</span> <span>Parser</span> <span>{</span>
<span>public</span> <span>function</span> <span>count</span><span>(</span><span>string</span> <span>$filepath</span><span>)</span> <span>{</span>
<span>printf</span><span>(</span><span>&#34;I&#39;m counting JSON objects from file %s pretty hard!</span><span>\n</span><span>&#34;</span><span>,</span> <span>$filepath</span><span>);</span>
<span>}</span>
<span>}</span>
<span>class</span> <span>Shipment</span>
<span>{</span>
<span>private</span> <span>Parser</span> <span>$parser</span><span>;</span>
<span>public</span> <span>function</span> <span>__construct</span><span>(</span><span>Parser</span> <span>$parser</span><span>)</span> <span>{</span>
<span>$this</span><span>-&gt;</span><span>parser</span> <span>=</span> <span>$parser</span><span>;</span>
<span>}</span>
<span>public</span> <span>function</span> <span>count</span><span>(</span><span>string</span> <span>$filepath</span><span>)</span> <span>{</span>
<span>$this</span><span>-&gt;</span><span>parser</span><span>-&gt;</span><span>count</span><span>(</span><span>$filepath</span><span>);</span>
<span>}</span>
<span>}</span>
<span>$shipment</span> <span>=</span> <span>new</span> <span>Shipment</span><span>(</span><span>new</span> <span>Parser</span><span>());</span>
<span>$shipment</span><span>-&gt;</span><span>count</span><span>(</span><span>&#34;/my/superb/shipment&#34;</span><span>);</span>
</code></pre><p>
We override here the method <code>count</code> in our subclass <code>JSONParser</code>. Now, our class <code>Shipment</code> will “work” if we pass to our new <code>Shipment</code> both <code>Parser</code> or <code>JSONParser</code>, in the sense that no type error will be thrown.
</p><p>
But will it works as intended? What parser to use when we want an instance of <code>Shipment</code>? A <code>Parser</code>? A <code>JSONParser</code>?
</p><p>
We could look at the implementation of <code>Shipment</code>, then at the implementation of both <code>JSONParser</code> and <code>Parser</code> to decide what behavior we need depending on the context. But we create objects and abstract behavior not to look at their implementations. It frees us some precious brain power to think about the part of the system we want to modify.
</p><p>
Mixing overriding and polymorphism is a recipe for disasters. In an inheritance tree with more classes and more overidding, you need to know what superclass override what behavior, if the subclass override what the superclass overrided, and so on.
</p><p>
This confusion between inheritance of implementation and specialization led to the Liskov Substitution Principle (LSP).
</p><h2>
The Liskov Substitution Principle
</h2><picture><source srcset="https://thevaluable.dev/images/2021/inheritance/lsp.webp" type="image/webp" src="https://thevaluable.dev/images/2021/inheritance/lsp.webp"/><img src="https://thevaluable.dev/images/2021/inheritance/lsp.jpg" alt="The LSP is about substituting a parent class with its base class"/></picture><p>
Barbara Liskov was a researcher who won the <a href="https://en.wikipedia.org/wiki/Turing_Award" target="_blank" rel="noopener">Turing Award</a> for her work on abstract data types. When she was asked to talk at the keynote of <a href="https://en.wikipedia.org/wiki/OOPSLA" target="_blank" rel="noopener">OOPSLA</a> in 1987, she looked at the papers about inheritance hierarchies and how developers were using them. She was pretty disappointed.
</p><p>
This keynote led to the paper <a href="https://www.cs.tufts.edu/~nr/cs257/archive/barbara-liskov/data-abstraction-and-hierarchy.pdf" target="_blank" rel="noopener">Data abstraction and hierarchy</a>. From there, some began to speak about a “Liskov Substitution Principle”, often quoting the following:
</p><blockquote><p>
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T.
</p></blockquote><p>
Who doesnt like good old academic writing full of <code>S</code>, <code>T</code>, <code>o2</code>, and friends? Heres a clearer way to define the same idea:
</p><blockquote><p>
Objects of subtypes should behave like those of supertypes if used via supertype methods.
</p></blockquote><p>
Its with this paper that the concept of <em>subtyping</em> was born.
</p><p>
This solves the problem of substitution of subclasses. According to Liskov, if you want to use polymorphism with inheritance, you need to have proper <em>subtyping</em>. How? By following this rule: when you substitute one superclass by its subclass, the <em>behavior</em> of the whole system should be <em>unchanged</em>.
</p><p>
Said differently: dont override anything.
</p><p>
With this definition of the LSP, and to come back to our example above, there wont be any doubt about the consequences substituting <code>Parser</code> by its subclass <code>JSONParser</code>. We are sure it wont have unexpected results because <code>JSONParser</code> doesnt override any behavior of its superclass. Said differently, you would always use the same method <code>parse</code> from <code>Parser</code> whatever the subclass of Parser you use.
</p><p>
But between Barbara Liskovs first statement and now, the LSP changed. From “the behavior should stay the same”, we know think that “the behavior shouldnt break the application”. This last definition is more ambiguous: how do we know that our system still behave correctly? What does it mean? Do we have every possible tests to ensure that its the case? If our system doesnt break but doesnt follow the specifications either, is it a violation of the LSP?
</p><p>
“But wait!” interrupts suddenly Dave. “This is not the Liskov Substitution Principle! This is not how its defined in the Holy SOLID Principles!”
</p><p>
Davina sigh. “The SOLID principles should be the D principle. The last one is the only one we can still save. The others are misinterpretations of important ideas when theyre not bad ideas. The LSP is a misinterpretation: the definition given by Robert Martin has not much to do with the definition given by Barbara Liskov.”.
</p><p>
Lets look at Martin definition of the LSP:
</p><blockquote><p>
All implementations of interfaces are subtypes of an interface.
</p></blockquote><p>
From the example above, the class <code>JSONParser</code> implements the same interface as <code>Parser</code>, but one method <code>count</code> count the number of lines and the other count the number of JSON objects. Interface substitution is not what Barbara Liskov was speaking about, and it wont save your codebase if you try to mix specialization and overriding.
</p><p>
Often, <a href="https://wiki.c2.com/?LiskovSubstitutionPrinciple" target="_blank" rel="noopener">developers dont like strong behavioral subtyping</a> as Barbara Liskov defined it. Thats why the principle was transformed over time. Often, inheritance is used to override the implementation of a superclass. On that regard, its interesting to note that inheritance is only interesting for Liskov in the context of subtyping; she doesnt see any value to use it for inheriting implementation.
</p><p>
Why? Because inheritance is not the only solution for code reuse. Many prefer using composition.
</p><h2>
Composition vs Single Inheritance
</h2><p>
While the open space is still silent, you begin to feel the tension dropping. The magic word has been pronounced: composition. While inheritance is a demon which tries to eat companies and their employees, composition is the solution to every possible disaster.
</p><h3>
Composition, Delegation, or Aggregation?
</h3><p>
“Are we speaking about composition, delegation, or aggregation here?” asks Dave. “Whats the difference?” ask another colleague.
</p><p>
Dave, with a smile, begins his explanation: “Look at the examples we were speaking about. A <code>JSONParser</code> <em>is a</em> parser, so inheritance makes sense in that case. But, for example, a <code>Shipment</code> <em>has a</em> parser, thats why we used composition”.
</p><p>
You intervene: “does it make sense to say that a shipment <em>has a</em> parser? No, here were speaking about delegation: the shipment <em>use a</em> parser, it delegates a task to a parser object. Thats all.”
</p><p>
“Really?” begins another colleague. “Are you sure a JSON parser <em>is a</em> parser, or does it <em>has</em> the behavior of a parser?” Another colleague takes part of the conversation: “No! Our <code>Shipment</code> <em>own a</em> <code>Parser</code>, so were speaking about aggregation here!”
</p><p>
Outside, the thunder growl again. Everybody begins to speak at the same time, throwing at each other <em>is-a</em>, <em>has-a</em>, <em>part-of</em>, <em>add-to</em>, and other pair of very short words you can link with a hyphen.
</p><p>
Davina listens to the conversation carefully, and when everybody calms down, she gives her opinion: “I see these <em>is-a</em> or <em>has-a</em> tricks all over the Internet. I also saw many developers defining different flavors of composition, delegation, aggregation, and whatnot. At the end, our problem are often so specific they dont fit any of these definitions. They are useless in practice. Dont use them”.
</p><p>
She continues. “Using natural language tricks (like <em>is-a</em> or <em>has-a</em>) to decide what solution we should apply to a problem is ambiguous, as we just witnessed. Its one of the reason why Mathematical notation was invented at the first place: to avoid the ambiguity of natural language. My advice: dont use these tricks to decide what solution you should use.”
</p><p>
To understand what Davina is speaking about, lets get back to our class <code>Shipment</code>:
</p><pre><code><span>&lt;?php</span>
<span>class</span> <span>Shipment</span>
<span>{</span>
<span>private</span> <span>JSONParser</span> <span>$parser</span><span>;</span>
<span>public</span> <span>function</span> <span>__construct</span><span>(</span><span>JSONParser</span> <span>$parser</span><span>)</span>
<span>{</span>
<span>$this</span><span>-&gt;</span><span>parser</span> <span>=</span> <span>$parser</span><span>;</span>
<span>}</span>
<span>public</span> <span>function</span> <span>import</span><span>(</span><span>string</span> <span>$filepath</span><span>)</span>
<span>{</span>
<span>$this</span><span>-&gt;</span><span>parser</span><span>-&gt;</span><span>parse</span><span>(</span><span>$filepath</span><span>);</span>
<span>}</span>
<span>}</span>
</code></pre><p>
When we want to create an instance of <code>Shipment</code>, we need to inject an object of type <code>JSONParser</code>. It doesnt matter if its called aggregation, composition, or delegation. At the end, it boils down to the same simple mechanism: injecting an object into another one.
</p><h3>
Is Composition Better Than Inheritance?
</h3><p>
We all know the Mantra of Composition, the one which will bless your codebase with the benediction of The Hasa and the Partof Gods. If you dont know it yet, Im sure youll hear it a good hundred of times in your career:
</p><blockquote><p>
Favor object composition over class inheritance.
</p></blockquote><p>
This is from the book <a href="https://www.goodreads.com/book/show/85009.Design_Patterns" target="_blank" rel="noopener">Design Patterns</a>, written by the Gang of Four. With a name like that, Im not sure if they were trying to force some general-but-specific (admire the paradox) solutions to our poor codebases or if their real goals were to create the mafia of software developers. One thing is certain: this book gave to beginners the perfect pretext to show how smart they are by instantly changing a healthy codebase into a legacy mess full of Singleton and Abstract Factories.
</p><p>
Like many, Im no innocent: Ive chanted the Mantra of Composition for years, without looking at the Mantra in its context. But context is important.
</p><p>
So, what our <del>godfathers</del> Gang of Four are saying just after enlightening the world with their Mantra?
</p><blockquote><p>
You should be able to get all the functionality you need just by assembling existing components through object composition. But this is rarely the case, because the set of available components is never quite rich enough in practice. Reuse by inheritance makes it easier to make new components that can be composed with old ones. Inheritance and object composition thus work together.
</p></blockquote><p>
According to this book, we should favor composition not because inheritance is evil, but because nobody uses it correctly. Well, hopefully we understand it better now.
</p><h4>
The Benefits of Composition
</h4><p>
Composition is very useful indeed. Lets imagine that we inject object A into object B. Here are the benefits:
</p><ul><li>If Object A is properly encapsulated, nobody will destroy anything by changing objects A inner implementation. Object B can suffer the change, but nothing else down the road.</li><li>You can use only part of the objects implementation. Object B doesnt automatically inherit from the whole object A.</li></ul><p>
To get back to our <code>Shipment</code> example, this means that the object <code>JSONParser</code> we inject is tightly coupled to the class <code>Shipment</code>, but this coupling stop there. If you instantiate <code>Shipment</code> later and you change the implementation of <code>JSONParser</code>, the class <code>Shipment</code> might need to change, and thats all.
</p><p>
As we saw with inheritance, the problem of tight coupling (or the benefit of cohesion) will affect <em>every layer down the inheritance tree</em>.
</p><h4>
The Drawbacks of Composition
</h4><p>
Composition is not the best solution when you want to use many objects or objects with a lot of behavior.
</p><p>
Lets say that you want to use 10 methods from 3 different objects and you want to add some implementation on top: youll need to inject your 3 objects, create 10 methods in your new class wrapping the 10 methods of the objects injected, and add more methods to take care of your new functionality.
</p><p>
“But I could directly call the object <code>JSONParser</code> from an instance of <code>Shipment</code>”, cut Dave.
</p><p>
“Thats true, answers Davina. But it would break the encapsulation of our objects <code>Shipment</code> in that case. It means that everything using the object <code>Shipment</code> would be tightly coupled to the object <code>JSONParser</code>. Encapsulation is broken.”
</p><p>
Composition doesnt bring you the benefits of subtyping either. When you inject an object to a class, youll need to inject a precise object if your language has some sort of type checking, and nothing else. On that regards, it constrains you (which can be a good thing!). If your language doesnt have any type checking and you can just give any object to the constructor of your class, it doesnt mean that it will <em>work as intended</em>. The problem stays the same.
</p><p>
At least, an inheritance hierarchy can indicate what object you can use instead of another and, if it follows a strict form of LSP, nothing should break.
</p><p>
“Thats wrong!”, shoot Dave, suddenly. “What about the interface construct? I love these, and you can do some good polymorphism with them without using the Demon of Inheritance!”
</p><p>
“Youre right, answers Davina. But using interface constructs is only using another form of inheritance.”
</p><h2>
Single and Multiple Inheritance Dumbed Down
</h2><picture><source srcset="https://thevaluable.dev/images/2021/inheritance/less_powerful.webp" type="image/webp" src="https://thevaluable.dev/images/2021/inheritance/less_powerful.webp"/><img src="https://thevaluable.dev/images/2021/inheritance/less_powerful.jpg" alt="Over the years, inheritance has seen less powerful implementations."/></picture><p>
As we saw, inheritance is very powerful, because you can mix reuse of implementation and subtyping in a hierarchy tree as deep as you want it to. This power is its biggest problem: many developers, not knowing all the implications we saw above, have a tendency to misuse inheritance, tightly coupling everything in huge inheritance hierarchies, which led to the Mantra of Composition. Thats why many gave up on single inheritance.
</p><p>
But multiple inheritance is even more flawed: the possibility for a subclass to have more than one superclass is making everything very ambiguous. As an example, you can look at the <a href="https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem" target="_blank" rel="noopener">diamond problem</a>. Additionally, multiple inheritance is very complex to implement in a programming language.
</p><p>
Thats why the designers of Objective-C and Java restricted inheritance with the protocol and the interface construct respectively. The benefits?
</p><ol><li>A class is forced to use the interface given by a protocol, which guarantee that each subtype has the same interface (but doesnt guarantee that the substitution will work!).</li><li>There is no multilevel inheritance anymore.</li><li>No more inheritance of implementation: the protocol is only an interface.</li><li>Multiple inheritance of interfaces is much easier to manage than multiple inheritance of implementations.</li></ol><blockquote><p>
Please note that even Java includes a limited form of multiple inheritance: inheritance of interfaces.”
</p></blockquote><h2>
Concrete Use of Inheritance
</h2><p>
We saw already some potential use of inheritance, but can we be more concrete? Over the years Ive come up with this set of rules:
</p><ol><li>Never use inheritance for classes which are related to the <em>business domain</em>.</li><li>Sometimes use inheritance for reusing implementation of classes bringing some <em>mechanics</em>.</li><li>If you use subtypes, always try to respect the original strictness of LSP as much as possible.</li><li>If you use the interface constructs for subtyping, keep in mind that the implementation of the interface can still break everything.</li></ol><p>
These rules are from my experience. Dont use them as Mantras working in every situation. We should experiment carefully with them and use our brain to see if the technical solutions fit the problem you have.
</p><h3>
Inheritance and Domain Objects
</h3><p>
Davina explain further: “When I speak about domain objects, I mean all the objects which are related to the business we work for. In our present case, in MegaCorpMoneyMaker, it would be classes like <code>Shipment</code>, <code>Order</code>, or <code>Product</code>.”
</p><p>
Introducing any form of hard coupling or <a href="https://thevaluable.dev/dry-principle-cost-benefit-example/">premature abstractions</a> with these objects is always dangerous. They are the <em>representation</em> of real world constructs, and since the real world change in unpredictable manners, these objects will change in unpredictable manners too. Keep them isolated as much as possible from the <em>mechanical parts</em> of your system.
</p><h3>
Inheritance and Mechanics
</h3><p>
You ask Davina: “what do you mean by mechanics”?
</p><p>
“These classes are everything which are not domain objects. For example, our classes to parse files represent some mechanisms: they dont represent anything from our business, theyre just general constructs to parse some files. Objects of this sort are often more general and can be applied in many more contexts than our precise business domain.”
</p><p>
For example, its not very likely that the world will come up tomorrow with a different definition of stacks. Thats why the object <code>Stack</code> wont need many changes overtime.
</p><p>
Anything representing mathematical constructs are good examples too. After all, Mathematics try to be as disconnected as possible from the real world. Its when you try to use mathematical concepts on the real world that everything begins to break. Thats what we call applied Mathematics.
</p><p>
If we think about it, inheritance create a hierarchy where its elements are not encapsulated with each others, but the hierarchy itself is encapsulated from its outside. We create a new construct doing so, an aggregation of objects. In that case, inheritance can be useful if you have to codify a general and coherent set of ideas where the properties and behaviors of the different objects will rarely change, or when the whole hierarchy needs to change when one of its member change predictably.
</p><h2>
Modern Languages and Inheritance
</h2><p>
Modern languages often take the decision to implement inheritance differently from “older” languages like Java, Python, Ruby, or PHP. They try hard to differentiate subtyping and inheritance of implementation. For example, in Rusts documentation:
</p><blockquote><p>
If a language must have inheritance to be an object-oriented language, then Rust is not one. There is no way to define a struct that inherits the parent structs fields and method implementations.
</p></blockquote><p>
But Rust implement some form of inheritance I didnt cover here: traits. If you need some polymorphism, Rust give you generic programming like many other languages.
</p><p>
Another example: Golang. You cant do any inheritance of implementation, only composition is allowed. You can also use interface constructs if you want some inheritance of interface.
</p><h2>
Inheritance Is Not Evil
</h2><picture><source srcset="https://thevaluable.dev/images/2021/inheritance/inheritance_not_evil.webp" type="image/webp" src="https://thevaluable.dev/images/2021/inheritance/inheritance_not_evil.webp"/><img src="https://thevaluable.dev/images/2021/inheritance/inheritance_not_evil.jpg" alt="Inheritance is not evil, it&#39;s just too powerful."/></picture><p>
What did we see in this article?
</p><ul><li>Inheritance create a hierarchical construct composed of <code>superclasses</code>, <code>subclasses</code>, and <code>base classes</code>.</li><li>Many common programming languages allow us to create a tree with infinite depth (multilevel inheritance) and width (hierarchical inheritance).</li><li>Your system can become hard to maintain if you mix two of the Three Power Gems of Inheritance in the same soup:<ul><li>Inheritance of implementation.</li><li>Substituting superclasses with their subclasses (subtyping if it follows some rules).</li><li>Multilevel inheritance.</li></ul></li><li>Most of the time, composition seems to be the best alternative to inheritance of implementation.</li><li>Everything is tightly coupled in an inheritance hierarchy, but this blurb of classes is still encapsulated from the outside.</li><li>Inheritance was a crude concept defined at the beginning of OOP and later refined with, for example, the interface construct.</li><li>Inheritance can be useful for the part of your system which wont change too much (mechanical part).</li></ul><p>
If you need to retain one thing from all of that: dont use DRY, or inheritance, or composition before you understand clearly whats the problem youre trying to solve and its context. These concepts should be used when you refactor you code; consider the first writing as a messy draft and, in that spirit, defer all the important decisions making your design hard to change as much as you can.
</p><p>
What should be together and what should not (cohesion) is one of these important decision. What should be under a layer of indirection using interface constructs is another.
</p><p>
If you think its a good idea to use an inheritance hierarchy, begin with a small one and see how it behaves in your system overtime.
</p><p>
Davina concludes:
</p><p>
“The concept of inheritance was refined over the years and gave us the constructs we use today, like the interface construct. In that sense, inheritance is definitely a pillar of the OOP paradigm. But its true that mixing features which are not necessarily orthogonal make inheritance difficult to harness in many programming languages.”
</p><p>
Everybody is silent now. The storm outside stopped. Dave is thinking hard, like everybody in the open space of MegaCorpMoneyMaker.
</p><p>
At the end, you should always read the documentation of the programming languages youre using to see exactly how they implement inheritance. Youll now be able to guess why the languages designers made their decisions and how you can use their implementation of inheritance effectively.
</p></div>