Inheritance versus composition
The idea of "inheritance" is something that most students learn about early on when they are studying object-oriented programming (OOP). But one of the seminal books about OOP recommends favoring "composition" over inheritance. Ariel Ortiz came to PyCon in Cleveland, Ohio to describe the composition pattern and to explain the tradeoffs between using it and inheritance.
Ortiz is a full-time faculty member at Tecnológico de Monterrey, which is a private university in Mexico. He noted that the title of his talk, "The Perils of Inheritance", sounded like "clickbait"; he jokingly suggested that perhaps he should have named it: "4 dangers of inheritance; you won't believe number 3!". That elicited a good laugh, but he said that clickbait was not his intent.
He has been teaching computer science for more than 30 years, using many languages, including Python. He likes Python and uses it for several courses, including data structures, web development, and compiler construction. He started with Python 2.0 in 2001 or so.
Definitions
In order to proceed, Ortiz said, he needed to define the two concepts at hand. "Inheritance" in the OOP sense starts with one class, known as the base class or superclass, and another class that is related to it. That second class is known as the derived class or subclass. The derived class has various attributes, such as variables and methods, that have come directly from the base class. Those attributes can be overridden in the derived class; new attributes can be added as well.
"Composition", on the other hand, has the composite or wrapper class and a component that is being wrapped, sometimes known as the "wrapee". Inheritance embodies the idea of an "is a" relationship, while composition creates a "has a" relationship.
In Python terms, you might have a Vehicle class that is used as the base for a derived Bicycle class, which would be created as follows:
class Bicycle(Vehicle): passSo, a Bicycle "is a" Vehicle. An example of composition might be a class Engine that is used by another class:
class Car: def __init__(self): self.engine = Engine()So, a Car "has a" (an) Engine.
![Ariel Ortiz [Ariel Ortiz]](https://static.lwn.net/images/2019/pycon-ortiz-sm.jpg)
The famous Design Patterns: Elements of Reusable Object-Oriented Software book has suggested favoring composition over inheritance. At the time it was published, over 20 years ago, most OO programmers were favoring inheritance in languages like C++ and Java. But, even all these years later, inheritance is the first and main tool that programmers reach for as part of their code reuse strategy.
Inheritance and composition are two mechanisms for code reuse. Inheritance is "white-box" (or even better, "crystal-box", Ortiz said) reuse. The derived class can see (too) many of the details of the base class. He prefers "black-box" reuse, which is what composition provides. The implementation of the component object is kept as a secret from the composite class. That secrecy is not because the object developer does not want people to know how it does its job, but because knowing those details is irrelevant. The component object can simply be used through its interface without needing to know anything further about its guts.
There are some advantages to inheritance, however. It is the easiest way to reuse code, he said; in Python you just list the base classes that you want to inherit from in the derived class definition. It is also easy to change the inherited implementation and to add to it.
The disadvantages of inheritance exist as well, of course. For one, the relationship between the base and derived classes is statically fixed; technically, that is not true for Python because of its dynamic nature, but is for C++ and Java. Inheritance supports weak encapsulation, so that derived classes can use parts of the base class in ways that the designer did not intend; also, name collisions can occur.
Beyond that, derived classes get everything from the base class, even things that they don't want. As the creator of Erlang, Joe Armstrong, put it: "You wanted a banana but what you got was a gorilla holding the banana and the entire jungle." And, finally, any changes in the base class impact all of the classes deriving from it. So, for example, adding a new method to the base class can cause name collisions all over the hierarchy.
Example
In order to show some of the differences between inheritance and composition, he presented two implementations of a simple last-in-first-out (LIFO) linked list that can be seen in a GitHub repository (and in his slides or the YouTube video of the talk). The code implements LinkedList as one class along with an InsertCounter class that is meant to count all of the insertions done to the list (it ignores removals from the list so the value is not the same as the length).
The example is a bit contrived, perhaps, but it does show that changing the implementation of the insert_all() method (which takes an iterable and adds each element to the list) in the base class actually affects the derived class. It leads to the count being incorrect in the InsertCounter object.
The composition version passes the LinkedList into the initialization of the InsertCounter:
counter = InsertCounter(LinkedList())After that, the counter object can be used in code that looks exactly the same as it was for the inheritance version:
counter.insert(42) counter.insert_all([1, 2, 3]) counter.remove() print(f'len = {len(counter)}, counter = {counter.counter}')The final print statement would yield:
len = 3, counter = 4
But, as Ortiz pointed out, there is lots that InsertCounter gets for free when it uses inheritance that needs to be explicitly added to the class when it uses composition. Many methods (e.g. remove(), clear(), __len__(), etc.) that came with the LinkedList via inheritance needed to be implemented for the class when it uses composition. On the other hand, the composed InsertCounter was not affected by the changes to LinkedList. But, on the gripping hand, the implementation of insert_all() was, presumably deliberately, set up to be susceptible to this problem; a more realistic example would be hard to fit into the 30-minute slot for the talk.
Ortiz pointed to the forwardable module as a way to avoid having to implement all of that extra code for the composite object (InsertCounter). The module makes it easy to delegate functionality from the composite object to the component. His last change to his example code used forwardable in the InsertCounter.
There are several advantages to composition. The implementation of the component can be chosen at runtime because it can be passed in as part of the initialization of the composite object. Interface changes have a limited ripple effect. If the component interface changes, only the code in the composite object needs to change; users of the composite object are unaffected. With composition, one can create a class that has relationships with multiple components; this is a design pattern known as the façade pattern.
There are, of course, disadvantages to composition. It requires more code than inheritance, as we saw, Ortiz said. It is often more difficult to read the code using composition than it is to read code using inheritance.
His talk was not meant to demonize inheritance, there are good reasons to use it sometimes. In particular, when the base and derived classes are in the same package and are under the control of the same developers, inheritance may well be the right choice. "Inheritance is not wrong", he said, there are many circumstances where it makes sense, especially when there truly is an "is a" relationship.
He ended with a Spanish question that was evidently made popular in a US advertisement: "¿Por qué no los dos?", which means "Why not both?". Inheritance and composition are both useful tools; developers should understand when to use each.
[I would like to thank LWN's travel sponsor, the Linux Foundation, for
travel assistance to Cleveland for PyCon.]
Index entries for this article | |
---|---|
Conference | PyCon/2019 |
Python | Inheritance |
Posted May 9, 2019 4:52 UTC (Thu)
by dmaas (guest, #38073)
[Link]
Posted May 9, 2019 7:09 UTC (Thu)
by avheimburg (guest, #75272)
[Link] (5 responses)
I read a great distinction by - I think - Martin Fowler: Inheritance should not be used for code reuse. Inheritance should only be used to model is-a relationships, the code reuse is incidental. If you just want code reuse, use composition or other patterns.
Posted May 9, 2019 12:33 UTC (Thu)
by excors (subscriber, #95769)
[Link] (3 responses)
I've seen it said that inheritance is not a way for new code to reuse old code, it's a way for old code to reuse new code.
I think the important distinction is whether the objects are being managed by the old code or the new code. ("Managed" in the sense of dealing with object lifetimes, choosing when to call methods, etc). If you're writing a new application that uses an old library, there's little need for inheritance; the application can manage its own objects that contain library objects via composition. So the library should be designed with a composition-friendly API (e.g. use explicit callback functions, don't use pure virtual functions that the application is meant to implement in a derived class).
But if you're writing e.g. an application that is going to be extended with new plugins, then (polymorphic) inheritance is very useful: the application can manage the plugin objects without having to know their implementation details at compile time. And multiple applications can support the same plugin interface, so the same plugins can be reused across them all.
Library-style APIs are far more common than plugin-style APIs, so inheritance should be used rarely, but it should still be used in the latter cases.
(There's also a distinction between the concept of inheritance and the mechanism of inheritance. E.g. the Python "forwardable" module doesn't use Python's inheritance mechanism, but I'd argue that it essentially *is* inheritance, because it's making an object that looks and behaves like a higher class of object and can be passed to code that expects the higher object. Meanwhile C++ like "class Derived : private Base {...}" (note the "private") is really composition, not inheritance. The concept is the important part, the rest is just implementation details.)
Posted May 13, 2019 10:04 UTC (Mon)
by dgm (subscriber, #49227)
[Link]
It looks to me that part of the problem with inheritance is that it couples interface definition with code reuse. When you get it right it can save you some work, but it's a very difficult problem, similar to designing thread-safe code, so we tend to get it wrong more often than right. And fixing base classes is such a pain in the ass...
Posted May 14, 2019 11:19 UTC (Tue)
by jezuch (subscriber, #52988)
[Link]
If you mean by that the pattern where the base class declares a bunch of abstract methods that are supposed to be implemented by the inheritor to be called from the base class, then code like this is horrible and deserves to die. It's especially problematic when these methods are called from the constructor because (in Java) the method can see final fields before they are initialized. I've see too many null pointers coming from this and it's not fun.
Posted Jul 30, 2020 13:04 UTC (Thu)
by RomanaMendes (guest, #140492)
[Link]
Posted May 14, 2019 11:36 UTC (Tue)
by lambdacat (guest, #120899)
[Link]
Posted May 9, 2019 7:52 UTC (Thu)
by marcH (subscriber, #57642)
[Link] (16 responses)
One of the main reasons composition should be preferred in general is because most programmers don't fully comprehend or can't remember (I can't) subtle topics like contravariance.
https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
For example, in C#, if Cat is a subtype of Animal, then:
"Object-oriented programming is an exceptionally bad idea which could only have originated in California."
One way object-oriented programming is bad is because it enshrines what should be contained as much as possible in computing: side-effects. Now hardware is all about registers and side-effects, so C and drivers have an excuse. Python or (much worse) Java not so much.
http://steve-yegge.blogspot.com/2006/03/execution-in-king...
Posted May 9, 2019 8:49 UTC (Thu)
by marcH (subscriber, #57642)
[Link] (12 responses)
Posted May 9, 2019 11:06 UTC (Thu)
by ibukanov (subscriber, #3942)
[Link] (5 responses)
React, Elm etc. are ultimate examples of this trends when everything is composed. But even QT has been encouraging this model for ages relining on inheritance mostly to expose low-level hooks that are not used in typical cases.
Posted May 9, 2019 17:46 UTC (Thu)
by iabervon (subscriber, #722)
[Link] (1 responses)
Posted May 12, 2019 16:33 UTC (Sun)
by bjartur (guest, #67801)
[Link]
Is it possible that you are in fact referring to subtyping and not inheritance of attributes and members? Subtypes are great. So are typeclasses and interfaces, of course. For your usecase, a struct or a typeclass/interface would probably be best. A concrete Widget class should not exist, because rendering a vanilla Widget is meaningless without size, appearance and the handler for clicks. In Python, there should probably be a widget function that accepts these parameters (and perhaps has some additional, optional parameters as well) and returns a Widget struct. Lesson 1: passing information by overriding inherited, public members creates the risk of failing to override a necessary value. A struct, typeclass, interface or abstract class must be used instead.
You might still be tempted to set defaults in a superclass and allow a subclass to override them. This is just duck-typing with defaults, and if your language allows implicit overrides, you're just risking name collusion. Look for example at the following class hierarchy:
Posted May 9, 2019 19:11 UTC (Thu)
by mfuzzey (subscriber, #57966)
[Link]
There is normally a "graphical object" base class from which all elements derive and a hierarchy of container types (window, panel etc).
Where it goes wrong is when inheritance is used for purely implementation reasons.
Posted May 10, 2019 10:08 UTC (Fri)
by LtWorf (subscriber, #124958)
[Link] (1 responses)
Posted May 14, 2019 6:25 UTC (Tue)
by massimiliano (subscriber, #3048)
[Link]
In GUI you want inheritance. You don't want every widget to have different getWidth() methods which do not inherit from a superclass. It becomes a nightmare.
In game frameworks this is often solved with and "entity component system" design.
If an "entity" has the "sizable" component, then it supports "getWidth()" (implemented in the component, for all entities that use it).
It is a lot like mixins, but properly formalized in the framework. It should work for GUIs, too, but these days my personal preference is the way React and Elm handle GUI composition. In fact, I ended up implementing a simple React-like framework in Unity3D to manage scene graph mutations (and the Unity3D scene graph is entity-component based...).
Posted May 9, 2019 11:44 UTC (Thu)
by eru (subscriber, #2753)
[Link] (5 responses)
Most real-world things are made of objects that have some state. Recall object -oriented programming was invented in the context of simulation (the first language with the class concept was actually called SIMULA). OO models the world in a natural way.
Posted May 9, 2019 16:39 UTC (Thu)
by marcH (subscriber, #57642)
[Link] (2 responses)
> OO models the world in a natural way.
- Superficially yes: "structs" organize real-world data a very obvious way.
Now let's look at some list of the "most important OO concepts" https://www.amazon.com/Theory-Objects-Monographs-Computer...
Self, dynamic dispatch, classes, inheritance, protected and private methods, prototyping, subtyping, covariance and contravariance, and method specialization.
How many of these concepts still feel "natural" and related to the real-world? Genuine question.
- Now let's brush that aside and assume OO concepts are a great fit to model I/O with the real-world. Does that mean they're also a great fit for this thing that happens elsewhere, _between_ input and output? I think some call it "computing".
PS: if you're stuck with Java you really want to have a look at Kotlin.
Posted May 9, 2019 18:54 UTC (Thu)
by eru (subscriber, #2753)
[Link] (1 responses)
Posted May 9, 2019 20:49 UTC (Thu)
by marcH (subscriber, #57642)
[Link]
Yes; I think it's when you add methods and actual computation that OO gradually stops being "real-world". By that time it's too late, you're past the seduction phase.
Posted May 14, 2019 6:38 UTC (Tue)
by massimiliano (subscriber, #3048)
[Link]
If you are interested in the origins of OOP, and the topics of this talk, you might like a talk I did a few years ago contrasting OOP vs functional programming.
Some of the concepts are similar to the ones of this talk (even some quotes!), but the scope is broader, and instead of focusing on a single pattern I try to explain what went wrong between the clean vision that Alan Key had and the actual implementation of OOP in modern mainstream languages (mostly Java and C++).
Videos are on vimeo and youtube.
Enjoy :-)
Posted May 14, 2019 11:28 UTC (Tue)
by jezuch (subscriber, #52988)
[Link]
My opinion at least ;)
Posted May 9, 2019 11:01 UTC (Thu)
by jem (subscriber, #24231)
[Link]
Well, bear in mind that the talk was given at a Python conference.
Posted May 10, 2019 4:57 UTC (Fri)
by NYKevin (subscriber, #129325)
[Link] (1 responses)
In my opinion, you don't need to remember those, and focusing on memorizing them is likely to interfere with your understanding rather than enhance it.
Instead, teach yourself the Liskov substitution principle. All of the "rules of inheritance" (covariance and contravariance, method pre- and post-conditions, composition vs. inheritance, etc.) are just special cases of LSP, and can be derived as needed. Posted May 9, 2019 12:38 UTC (Thu)
by ballombe (subscriber, #9523)
[Link] (3 responses)
Posted May 9, 2019 14:03 UTC (Thu)
by Karellen (subscriber, #67644)
[Link] (2 responses)
Forget about small inefficiencies 97% of the time. First create a clear model of your data, and straightforward code to manipulate it. If you have performance issues, measure your bottlenecks, and fix the biggest problems that you actually have. It'll probably be a nested loop you put in by accident somewhere. If you find out that the way you're using inheritance or composition is your biggest performance issue, sure, then run some benchmarks and look at alternatives. Until then, don't worry about it.
Posted May 9, 2019 16:54 UTC (Thu)
by marcH (subscriber, #57642)
[Link]
=> Bottlenecks will never be where you expect them. Do not ever optimize without measuring (and your measurements will always be incomplete - but still much better than none)
"On the other hand, we cannot ignore efficiency"
=> Don't keep your CPUs busy when barely more code can avoid it
Posted May 11, 2019 15:45 UTC (Sat)
by marcH (subscriber, #57642)
[Link]
https://wiki.openjdk.java.net/download/attachments/118293...
https://wiki.openjdk.java.net/display/HotSpot/Performance...
Don't get me wrong: static optimizations performed at compile-time are just as insane. And of course CPUs also perform insane run-time optimizations - ever heard of Spectre?
=> Never try to predict performance ahead of time. It's way beyond the reach of any human brain.
Posted May 9, 2019 14:32 UTC (Thu)
by willy (subscriber, #9762)
[Link] (6 responses)
struct foo { int x; }
struct inheriting_foo { struct foo; }
struct composing_foo { struct foo *; }
Posted May 9, 2019 16:20 UTC (Thu)
by marcH (subscriber, #57642)
[Link] (5 responses)
Not really. As nicely phrased by excors above, this doesn't let: "old code use new code".
Posted May 9, 2019 20:03 UTC (Thu)
by logang (subscriber, #127618)
[Link] (4 responses)
I think the kernel largely uses composition, but there are also cases where inheritance is used. I would say inheritance is when there's an ops struct (ie. file operations and the VFS layer). Per excor's terminology, this allows old code (ie. the VFS layer and userspace) to use new code (ie. a new char or block device driver implementing a new fops struct).
However, the kernel often avoids the problems with inheritance by keeping layers thin so that changes to one layer doesn't have a huge affect on sub-objects. (ie. the VFS layer doesn't really do much that could interfere with the operations handled by the actual file objects). I think the problems discussed in this article with inheritance is very similar to the mid-layer mistake discussed in [1].
Posted May 9, 2019 20:39 UTC (Thu)
by marcH (subscriber, #57642)
[Link] (2 responses)
https://en.wikipedia.org/wiki/Virtual_method_table
Posted May 10, 2019 7:11 UTC (Fri)
by mjthayer (guest, #39183)
[Link] (1 responses)
struct foo { struct foo_ops * ops; ... };
...
foo base;
...
Posted May 10, 2019 16:46 UTC (Fri)
by logang (subscriber, #127618)
[Link]
There are lots of examples of the embedded struct approach (struct inheriting_foo) that don't have any corresponding ops structure. For example, struct kref.
There's also plenty of examples of ops structs that don't typically have a corresponding embedded struct (ie. struct file_operations).
So the point is that having a pointer to a structure or an embedded structure isn't a good indication of whether the pattern is inheritance or composition.
Posted May 10, 2019 17:53 UTC (Fri)
by nix (subscriber, #2304)
[Link]
I don't know of any object-oriented system employing extensive inheritance that has been capable of significant evolution over time while retaining compatbility, even when it was written in a language (like Java) where you could fiddle with superclasses without horrible ABI considerations like (say) C++ usually has. The most you see is periodic massive ABI and API breaks, breaking all consumers, where the logjam of pending changes to the superclasses and their interrelationships can finally break free and be implemented -- but then the new version gets users again and the whole thing freezes over once more.
This is, IIRC, more or less what killed Borland's old Turbo Vision system. It couldn't evolve to cope with even minor GUI changes, because it was inheritance-based, and all the users would have broken. So it got more and more outdated until it died. A shame: I liked it, but you canna argue with the laws of physics, uh, I mean development logic.
Posted May 9, 2019 17:20 UTC (Thu)
by walex (guest, #69836)
[Link] (2 responses)
When I read these discussions I am amused because I figured out long ago that inheritance is pointless, and there is a simple "trick" (that embeds a profound insight) that makes it completely pointless: Just allow any subfield of a record/structure to be named with any unique subset of its "path". That is, allow a.b.c.d to be accessed also as a.d (or a.c.d etc.) if that is unambiguous. This simple rule (plus overloading) gives all the semantics of both single and multiple inheritance, without any downsides. Amazing news from the internet!
Posted May 10, 2019 11:32 UTC (Fri)
by renox (guest, #23785)
[Link] (1 responses)
Posted May 10, 2019 23:13 UTC (Fri)
by walex (guest, #69836)
[Link]
I think that However I'll try to explain, consider in pseudo C with some liberties:
Somebody mischievous could says that in this view if struct A
"has"a field of type B also struct A "is" B, and multiple
inheritance from B and C just means that A has fields of type B
and C, and the names of those fields can be dropped or used
explicitly.
Posted May 10, 2019 9:33 UTC (Fri)
by martin.pitt (subscriber, #26246)
[Link] (4 responses)
So I don't really understand the fuss about these "recommendations"?
Posted May 12, 2019 13:18 UTC (Sun)
by bjartur (guest, #67801)
[Link] (1 responses)
Posted May 14, 2019 16:45 UTC (Tue)
by nybble41 (subscriber, #55106)
[Link]
> Inheriting visibility considered harmful The problem with your example is that you're not actually trying to model an "is a" relationship. True, a square is a rectangle—in geometry. But geometrical squares and rectangles don't have state; they're immutable. Neither a rectangle nor a square is an object with a "width" or "height" state which can be modified, and something with only one kind of mutable state is not a subtype of something with two distinct kinds of state. A more appropriate API for immutable geometric shapes would look like: > Inheritance causes accidental overriding
Posted May 12, 2019 17:58 UTC (Sun)
by giraffedata (guest, #1954)
[Link]
The talk (or report of it) gives poor examples of the dilemma. No self-respecting programmer would make Car inherit from Engine. It may save you some typing to do it (the article mentions that with respect to the "insert counter/linked list" inheritance), but you cannot get into heaven writing programs that tell the reader that a car is a kind of engine.
But there are cases where a program can be legitimately designed either way. You can say a Bicycle is a WheeledConveyance and a Car is a WheeledConveyance, or you can say a Bicycle has a Wheel and a Car has a Wheel. You have to think hard about that to decide which will be best, but the talk says you should lean toward the latter.
I note that it consistently says just to lean away from inheritance, not that inheritance is always bad.
Posted May 14, 2019 11:34 UTC (Tue)
by jezuch (subscriber, #52988)
[Link]
Posted May 10, 2019 16:52 UTC (Fri)
by ncm (guest, #165)
[Link] (1 responses)
I am forced to conclude it is the effect of Java coders leaking out. Java certainly steers them toward foolish inheritance. Bad habits die hard.
Posted May 10, 2019 18:02 UTC (Fri)
by excors (subscriber, #95769)
[Link]
Maintaining knowledge in an evolving community requires constant effort, and this is part of that.
Inheritance versus composition
Inheritance is not code reuse
Inheritance is not code reuse
Inheritance is not code reuse
Inheritance is not code reuse
Inheritance is code reuse
In (the existing) classes you have attributes or properties or composition ie what is the sub or new class is compose of .
The new class inherits/ reused from the old both:-
Has relationships from the attributes/ composition
& Also,
Is relationship from each class
Ps:-interfaces do not produce objects
Inheritance is not code reuse
Inheritance versus composition
- IEnumerable<Cat> is a subtype of IEnumerable<Animal>. The subtyping is preserved because IEnumerable<T> is covariant on T.
- Action<Animal> is a subtype of Action<Cat>. The subtyping is reversed because Action<T> is contravariant on T.
- Neither IList<Cat> nor IList<Animal> is a subtype of the other, because IList<T> is invariant on T.
Also:
https://dzone.com/articles/covariance-and-contravariance
https://stackoverflow.com/questions/18666710/why-are-arra...
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
What if the Widget class later adds a depth property to use for shading or other 3D effects? Rendering code will incorrectly use water pressure as depth for the widget implemented above. So instead, another abstract class (interface) must be created for widgets that want to expose a custom depth. Lesson 2: passing information overriding inherited, public members enables silent name collision.
from abc import ABC, abstractmethod
class Widget(ABC):
@abstractmethod
def on_click(self):
pass
@property
@abstractmethod
def width(self):
pass
@property
@abstractmethod
def height(self):
pass
class PressureGauge(Widget):
@property
def depth(self):
return gauge_pressure()
@property
def height(self):
return 5
@property
def width(self):
return self.depth
def on_click(self):
pass
Inheritance versus composition
That is useful and seems reasonable to me because it is a true "is-a" relationship.
For example AWT, Java's original GUI toolkit used inheritance for event bindings .
You actually had to sub class Button and override onClick() to add your event handling code! That was horrible but they fixed it in the second UI toolkit, Swing, by adding listeners and methods to add listeners to components (a composition based interface).
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
Wait: GUIs are made of "objects".
Inheritance versus composition
The JDK has a flat list of "All Classes "https://docs.oracle.com/javase/8/docs/api/
How often do these classes relate to the real-world? Genuine question; I haven't counted!
Inheritance versus composition
I wonder about your reference to I/O. An object is data and methods to operate it, so there is computation.
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
Buckley & Rose, Towards a Universal VM.
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition
struct inheriting_foo { struct foo base; ... };
void foo_method (struct foo *self, ...)
{ ...
self->ops->...(...);
... }
interiting_foo inherited;
base->ops = foo_ops;
inherited->base->ops = inherited_ops;
foo_method(&base);
foo_method(&inherited.base);
Inheritance versus composition
Inheritance versus composition
Inheritance is pointless, there is a simple little trick
Inheritance is pointless, there is a simple little trick
Inheritance is pointless, there is a simple little trick
“a.b can be handled by A”
is not a
clear/meaningful way of saying something, so I am not sure there
clarity of thought.struct Shape { char *name; };
struct Rect { struct Shape s; float x,y; }
struct Rect *r = { "r1", 2.0f, 3.0f};
float areaOf(struct Shape *s)
{ abort(); return 0.0/0.0; }
float areaOf(struct Rect *r)
{ return r->x*r->y; }
char *stringify(struct Shape *s)]
{ stringifyf("Shape{name: '%s'}",s->name); }
char *stringify(struct Rect *r)
{ stringifyf("Rect{name: '%s', x: %f, y: %f",
r->name,r->x,r->y); }
char *stringify_bis(struct Rect *r)
{ stringifyf("Rect{%s, x: %f, y: %f",
stringify(&r->s),r->x,r->y); }
Inheritance versus composition
Inheritance can not always be used to model "is a" relationships.
Inheritance versus composition
Inheriting visibility considered harmful
Public inheritance of an attribute implies inheriting the name and visibility of said attribute. But the subclass can place stronger semantics on inherited attributes. Those can lead to new invariants which should be preserved using attribute visibility.
An example of inheritance misleading users of a subclass:
This is not only surprising and almost useless. This is brittle. If the subclass decided to override the add
class Rectangle():
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class SometimesSquare(Rectangle):
def __init__(self, length):
super(SometimesSquare, self).__init__(length, length)
square = SometimesSquare(1)
square.height = 2 # does this update the width, to preserve the square shape?
square.width = 3 # does this update the height, to preserve the square shape?
print(square.area()) # which is printed 6 or 9?
length
as an attribute and override shape
to the seemingly equivalent definition return self.length * self.length
, it would drastically change the behavior. Instead of inheritance being an implementation detail, users of Square
need to know what and whether Square
inherits from Rectangle
and what it implements itself.
If the objective was not to inherit the semantics of Rectangle
(which include allowing the width and height to be different), but to reuse code, composition (or private inheritance) is better.
Now
class Square():
def __init__(self, length):
self._rectangle = Rectangle(length, length)
def area(self):
return self._rectangle.area()
square = Square(1)
square.height = 2
square.width = 3
print(square.area())
square.area()
returns 1. You can add setters for height
, width
and length
if you want, but the subclass does not inherit an incorrect default if you don't.
Inheritance causes accidental overriding
When inheriting, especially in Python, every member could by overriding a namesake in the superclass. Every new member in either the subclass or superclass, and every change of name of member, can easily cause subtle bugs. Thus inheritance creates an undue maintenance burden.
Inheritance versus composition
class Rectangle():
def __init__(self, width, height):
self._width = width
self._height = height
def width(self):
return self._width
def height(self):
return self._height
def updateWidth(self, width):
return Rectangle(width, self._height)
def updateHeight(self, height):
return Rectangle(self._width, height)
def area(self):
return self._width * self._height
def scale(self, factor):
return Rectangle(factor * self._width, factor * self._height)
class Square(Rectangle):
def __init__(self, length):
super(Square, self).__init__(length, length)
def length(self):
return self.width()
def updateLength(self, length):
return Square(length)
def scale(self, factor):
return Square(factor * self.length())
@classmethod
def fromRectangle(cls, rect):
if rect.width() == rect.height():
return cls(rect.width())
else:
return None
square = Square(1)
rect1 = square.updateWidth(2) # Fine, gives Rectangle(2, 1)
rect2 = square.updateHeight(3) # Fine, gives Rectangle(1, 3)
rect3 = square.updateWidth(2).updateHeight(2) # Gives Rectangle(2, 2), not Square(2)
print(Square.fromRectangle(rect1)) # not a square, so prints None
print(Square.fromRectangle(rect3).length()) # prints 2
print(square.area()) # still 1
Inheritance versus composition
If you have a composition problem (Engine/Car), use composition. If you have an inheritance situation (Vehicle/Bicycle), use inheritance.
Inheritance versus composition
Inheritance versus composition
Inheritance versus composition