|
|
Subscribe / Log in / New account

Inheritance versus composition

By Jake Edge
May 8, 2019

PyCon

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):
        pass
So, 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]

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
ConferencePyCon/2019
PythonInheritance


to post comments

Inheritance versus composition

Posted May 9, 2019 4:52 UTC (Thu) by dmaas (guest, #38073) [Link]

Just wanted to say that I'm always eager to read these summaries of conference talks. Jonathan's recent series on the Linux summit was great and I'm excited to read about PyCon too. These talk summaries are one of the top reasons I subscribe to LWN.

Inheritance is not code reuse

Posted May 9, 2019 7:09 UTC (Thu) by avheimburg (guest, #75272) [Link] (5 responses)

> But, even all these years later, inheritance is the first and main tool that programmers reach for as part of their code reuse strategy.

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.

Inheritance is not code reuse

Posted May 9, 2019 12:33 UTC (Thu) by excors (subscriber, #95769) [Link] (3 responses)

> Inheritance should not be used for code reuse.

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.)

Inheritance is not code reuse

Posted May 13, 2019 10:04 UTC (Mon) by dgm (subscriber, #49227) [Link]

The current trend seems to be that, for the use case you describe (namely, making old code call new code), the best approach is interfaces rather than inheritance.

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...

Inheritance is not code reuse

Posted May 14, 2019 11:19 UTC (Tue) by jezuch (subscriber, #52988) [Link]

> a way for old code to reuse new code

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.

Inheritance is code reuse

Posted Jul 30, 2020 13:04 UTC (Thu) by RomanaMendes (guest, #140492) [Link]

I invented class & inheritance in coding & inheritance means reuse of codes instead of the constant need to repeat codes.
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

Posted May 14, 2019 11:36 UTC (Tue) by lambdacat (guest, #120899) [Link]

this looks more correct

Inheritance versus composition

Posted May 9, 2019 7:52 UTC (Thu) by marcH (subscriber, #57642) [Link] (16 responses)

Wow, a comparison of composition and inheritance which barely mentions types - just indirectly at the very end - and never even uses the word.

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:
- 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...

"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...

Inheritance versus composition

Posted May 9, 2019 8:49 UTC (Thu) by marcH (subscriber, #57642) [Link] (12 responses)

Wait: GUIs are made of "objects". So maybe that's why object-oriented programming has been so successful and why we have all these side-effects and bugs: a very small bit of actual "computing" sandwiched between a lot of code to drive the hardware and a lot of code to display and interact with the data.

Inheritance versus composition

Posted May 9, 2019 11:06 UTC (Thu) by ibukanov (subscriber, #3942) [Link] (5 responses)

GUI is *composed* of elements. Using inheritance to model that gives a bad mismatch. Good GUI API do not use inheritance much if any but rather encourages composition and behavior customization using explicit properties, delegates and custom events.

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.

Inheritance versus composition

Posted May 9, 2019 17:46 UTC (Thu) by iabervon (subscriber, #722) [Link] (1 responses)

GUI is composed of elements, but each of those elements has some special behavior (what happens when you click on something, e.g.) and some common behavior (it occupies some space, e.g.). This means your GUI is full of things that are specific things to some code and generic things to other code in a way that's good to model with inheritance.

Inheritance versus composition

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:

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
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.

Inheritance versus composition

Posted May 9, 2019 19:11 UTC (Thu) by mfuzzey (subscriber, #57966) [Link]

Yes GUIs are composed of elements but each individual element will normally use inheritance.

There is normally a "graphical object" base class from which all elements derive and a hierarchy of container types (window, panel etc).
That is useful and seems reasonable to me because it is a true "is-a" relationship.

Where it goes wrong is when inheritance is used for purely implementation reasons.
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

Posted May 10, 2019 10:08 UTC (Fri) by LtWorf (subscriber, #124958) [Link] (1 responses)

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.

Inheritance versus composition

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...).

Inheritance versus composition

Posted May 9, 2019 11:44 UTC (Thu) by eru (subscriber, #2753) [Link] (5 responses)

Wait: GUIs are made of "objects".

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.

Inheritance versus composition

Posted May 9, 2019 16:39 UTC (Thu) by marcH (subscriber, #57642) [Link] (2 responses)

Never thought about the "SIMULA" name, interesting thx.

> 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".
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!

PS: if you're stuck with Java you really want to have a look at Kotlin.

Inheritance versus composition

Posted May 9, 2019 18:54 UTC (Thu) by eru (subscriber, #2753) [Link] (1 responses)

I suspect most of the benefits of OO could be realized by what already was in SIMULA. Classes and inheritance, that was about it. It also had automatic memory management and a built-in string type (more like Java than C++, but the syntax was based on Algol 60). It was one of the first languages I used, though I must admit at that time I did not take much advantage of the OO features. Just used it as a nicer alternative to Pascal in an exercise.
I wonder about your reference to I/O. An object is data and methods to operate it, so there is computation.

Inheritance versus composition

Posted May 9, 2019 20:49 UTC (Thu) by marcH (subscriber, #57642) [Link]

> I wonder about your reference to I/O. An object is data and methods to operate it, so there is computation.

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.

Inheritance versus composition

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 :-)

Inheritance versus composition

Posted May 14, 2019 11:28 UTC (Tue) by jezuch (subscriber, #52988) [Link]

AFAICT the "object orientedness" as defined in Simula is nothing like the object orientedness we came to know later. In fact only recently we started reinventing the original vision with actors and message passing paradigms like reactive programming. The OO we know is just an extension of structured programming with nothing revolutionary and a lot of confusion.

My opinion at least ;)

Inheritance versus composition

Posted May 9, 2019 11:01 UTC (Thu) by jem (subscriber, #24231) [Link]

>Wow, a comparison of composition and inheritance which barely mentions types - just indirectly at the very end - and never even uses the word.

Well, bear in mind that the talk was given at a Python conference.

Inheritance versus composition

Posted May 10, 2019 4:57 UTC (Fri) by NYKevin (subscriber, #129325) [Link] (1 responses)

> most programmers don't fully comprehend or can't remember (I can't) subtle topics like contravariance.

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.

Inheritance versus composition

Posted May 17, 2019 11:34 UTC (Fri) by mips (guest, #105013) [Link]

Inheritance versus composition

Posted May 9, 2019 12:38 UTC (Thu) by ballombe (subscriber, #9523) [Link] (3 responses)

What are the performance differences between them ?

Inheritance versus composition

Posted May 9, 2019 14:03 UTC (Thu) by Karellen (subscriber, #67644) [Link] (2 responses)

That depends, not only on the way that a given language models inheritance and composition (e.g. the way that Python and C++ model the features within those languages differs in significant ways), and in the way that any given implementation of a language implements the required model (e.g. CPython and PyPy may implement the same concepts in radically different ways), but also in the way that a given implementation of a given language micro-optimises the code to perform the actual steps at runtime.

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.

Inheritance versus composition

Posted May 9, 2019 16:54 UTC (Thu) by marcH (subscriber, #57642) [Link]

"Premature optimization is the root of all evil"

=> 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

Inheritance versus composition

Posted May 11, 2019 15:45 UTC (Sat) by marcH (subscriber, #57642) [Link]

"De-virtualization" (of virtual methods) is one of the main optimizations mentioned by this presentation from Hotspot JIT experts. That's one of the ways interpreted languages (e.g. Java) can sometimes be faster than compiled languages: because they "compile at run-time" when more information is available.

https://wiki.openjdk.java.net/download/attachments/118293...
Buckley & Rose, Towards a Universal VM.

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.

Inheritance versus composition

Posted May 9, 2019 14:32 UTC (Thu) by willy (subscriber, #9762) [Link] (6 responses)

For dinosaurs like me ...

struct foo { int x; }

struct inheriting_foo { struct foo; }

struct composing_foo { struct foo *; }

Inheritance versus composition

Posted May 9, 2019 16:20 UTC (Thu) by marcH (subscriber, #57642) [Link] (5 responses)

> struct inheriting_foo { struct foo; }

Not really. As nicely phrased by excors above, this doesn't let: "old code use new code".

Inheritance versus composition

Posted May 9, 2019 20:03 UTC (Thu) by logang (subscriber, #127618) [Link] (4 responses)

I agree, that's only really how the memory is allocated not a conceptual difference between composition vs inheritance. I would say both of willy's examples are composition.

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].

[1] https://lwn.net/Articles/336262/

Inheritance versus composition

Posted May 9, 2019 20:39 UTC (Thu) by marcH (subscriber, #57642) [Link] (2 responses)

> I would say inheritance is when there's an ops struct

https://en.wikipedia.org/wiki/Virtual_method_table

Inheritance versus composition

Posted May 10, 2019 7:11 UTC (Fri) by mjthayer (guest, #39183) [Link] (1 responses)

In other words, Matthew's example is correct, just somewhat brief?

struct foo { struct foo_ops * ops; ... };
struct inheriting_foo { struct foo base; ... };
void foo_method (struct foo *self, ...)
{ ...
self->ops->...(...);
... }

...

foo base;
interiting_foo inherited;
base->ops = foo_ops;
inherited->base->ops = inherited_ops;
foo_method(&base);
foo_method(&inherited.base);

...

Inheritance versus composition

Posted May 10, 2019 16:46 UTC (Fri) by logang (subscriber, #127618) [Link]

No...

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.

Inheritance versus composition

Posted May 10, 2019 17:53 UTC (Fri) by nix (subscriber, #2304) [Link]

Even so, when the semantics of ops changes, it's a bear to fix all the callers, so usually one introduces a new member and deprecates the old member, removing it only when there are no users left. This is *exactly* the problem with inheritance, only it's impossible for the 'removal' state ever to happen in a system where the inheritees come from different projects from the inheritors. The dream they sold in the 90s, and tried to implement with things like CORBA and SOM, was that you could have disconnected objects across the world inheriting from some widely-used super-objects, but in order for this to be useful the super-objects had to have semantics, and as soon as those semantics became complex enough to be useful (so it had users), the super-objects were essentially prohibited from evolving: the coupling was so tight that even side-effects usually became mandatory parts of the interface that were inexpressible in the type system but usually impossible to change anyway.

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.

Inheritance is pointless, there is a simple little trick

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!

Inheritance is pointless, there is a simple little trick

Posted May 10, 2019 11:32 UTC (Fri) by renox (guest, #23785) [Link] (1 responses)

?? This is not inheritance at all: in the inheritance a.b can be handled by A if there is no child or by a child with an overided method and this without changing the callers.

Inheritance is pointless, there is a simple little trick

Posted May 10, 2019 23:13 UTC (Fri) by walex (guest, #69836) [Link]

I think that “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.

However I'll try to explain, consider in pseudo C with some liberties:

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); }

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.

Inheritance versus composition

Posted May 10, 2019 9:33 UTC (Fri) by martin.pitt (subscriber, #26246) [Link] (4 responses)

I don't regard one of these (inheritance or composition) being inherently superior to the other. If you have a composition problem (Engine/Car), use composition. If you have an inheritance situation (Vehicle/Bicycle), use inheritance. Trying to use the wrong tool for a situation -- trying to pretend that a Bicycle has a Vehicle, or a Car is an Engine, just ends in super-ugly and buggy code no matter what your teacher's preference is.

So I don't really understand the fuss about these "recommendations"?

Inheritance versus composition

Posted May 12, 2019 13:18 UTC (Sun) by bjartur (guest, #67801) [Link] (1 responses)

Inheritance can not always be used to model "is a" relationships.
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:

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?
This is not only surprising and almost useless. This is brittle. If the subclass decided to override the add 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.

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())
Now 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

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:

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 causes accidental overriding

This is really an issue with the way Python handles symbols, not inheritance in general. If symbols are scoped under packages/modules/namespaces (as in Haskell or Common Lisp) and not blindly imported into the global namespace, unintentionally using the same name for distinct superclass and subclass members won't cause accidental overriding.

Inheritance versus composition

Posted May 12, 2019 17:58 UTC (Sun) by giraffedata (guest, #1954) [Link]

If you have a composition problem (Engine/Car), use composition. If you have an inheritance situation (Vehicle/Bicycle), use inheritance.

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.

Inheritance versus composition

Posted May 14, 2019 11:34 UTC (Tue) by jezuch (subscriber, #52988) [Link]

The fuss is about using inheritance to achieve code reuse, which, in your words, is the wrong tool here.

Inheritance versus composition

Posted May 10, 2019 16:52 UTC (Fri) by ncm (guest, #165) [Link] (1 responses)

I am amazed you can still give conference talks about this old chestnut.

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.

Inheritance versus composition

Posted May 10, 2019 18:02 UTC (Fri) by excors (subscriber, #95769) [Link]

What's wrong with giving talks about old topics? There's a continuous influx of new programmers who will eventually have to discover these ideas for the first time. They're probably not going to learn it at university - there's enough time to teach them the mechanics of how to use inheritance in toy examples in Java, but not enough to teach them good judgment about when to use it. That's something they have to develop themselves through practical experience (particularly by exercising bad judgment and then suffering the consequences of it), articles or talks that prompt them to draw the right conclusions from their experience, and discussions (like in the comments here) to help them refine and verify their thoughts.

Maintaining knowledge in an evolving community requires constant effort, and this is part of that.


Copyright © 2019, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds