Live data from Hacker News

When did people favor composition over inheritance?

sicpers.info

141–150 of 218 posts

Re: When did people favor composition over inheritance?

#141
post #43

An important point not mentioned by the article is that of "co-recursion" with inheritance (of implementation). That is: an instance of a subclass calls a method defined on a parent class, which in turn may call a method that's been overridden by the subclass (or even another sub-subclass in the hierarchy) and that one in turn may call another parent method, and so on. It can easily become a pinball of calls around t…

I think the fundamental issue with implementation-inheritance is the class diagram looks nice, but it hides a ton of method-level complexity if you consider the distinction between calling and subtyping interfaces, complexity that is basically impossible to encapsulate and would be better expressed in terms of other design approaches.

With interface-inheritance, each method is providing two interfaces with one single possible usage pattern: to be called by client code, but implemented by a subclass.

With implementation-inheritance, suddenly, you have any of the following possibilities for how a given method is meant to be used:

(a) called by client code, implemented by subclass (as with interface-inheritance) (b) called by client code, implemented by superclass (e.g.: template method) (c) called by subclass, implemented by superclass (e.g.: utility methods) (d) called by superclass, implemented by subclass (e.g.: template's helper methods)

And these cases inevitably bleed into each other. For example, default methods mix (a) and (b), and mixins frequently combine (c) and (b).

Because of the added complexity, you have to carefully design the relationship between the superclass, the subclass, and the client code, making sure to correctly identify which methods should have what visibility (if your language even allows for that level of granularity!). You must carefully document which methods are intended for overriding and which are intended for use by whom.

But the code structure itself in no way documents that complexity. (If we want to talk SOLID, it flies in the face of the Interface Segregation Principle). All these relationships get implicitly crammed into one class that might be better expressed explicitly. Split out the subclassing interface from the superclass and inject it so it can be delegated to -- that's basically what implementation-inheritance is syntactic sugar for anyway and now the complexity can be seen clearly laid out (and maybe mitigated with refactoring).

There is a trade-off in verbosity to be sure, especially at the call site where you might have to explicitly compose objects, but when considering the system complexity as a whole I think it's rarely worth it when composition and a tiny factory function provides the same external benefit without the headache.

These are powerful tools, if used with discipline. But especially in application code interfaces change often and are rarely well-documented. It seems inevitable that if the tool is made available, it will eventually be used to get around some design problem that would have required a more in-depth refactor otherwise -- a refactor more costly in the short-term but resulting in more maintainable code.

Re: When did people favor composition over inheritance?

#142
post #44

Earlier quoted context omitted.

I'm always surprised by how arrogant and unaware Python developers are. JavaScript/C++/etc developers are quite honest about the flaws in their language. Python developers will stare a horrible flaw in their language and say "I see nothing... BTW JS sucks so hard.". Let me give you just one example of Python's stupid implementation of inheritance. In Python you can initialize a class with a constructor that's not eve…

> In Python you can initialize a class with a constructor that's not even in the inheritance chain No, you can't. Or, at least, if you can, that’s not what you’ve shown. You’ve shown calling the initializer of an unrelated class as a cross-applied method within the initializer. Initializers and constructors are different things. > Oh, bonus point. I've see people creating a second constructor by calling `object.__new…

> Knowing that there are two constructors that exist for normal, non-native, Python classes, and that the basic constructoe Class.__new__, and that the constructor Class() itself calls Class.__new__() and then, if Class.__new__() returns an instance i of Class, also calls Class.__init__(i) before returning i, is pretty basic Python knowledge.

I didn't know most of that, and I've performed in a nightclub in Python, maintained a CSP networking stack in Python, presented a talk at a Python conference, implemented Python extensions with both C and cffi, and edited the Weekly Python-URL!

Re: When did people favor composition over inheritance?

#143

Gameplay logic inherently leans more towards composition, with a little hint of inheritance. You can have players and monsters, which are all types of "characters" or "units", which is inheritance, but instead of having a separate FlyingPlayer and a separate FlyingMonster, which use the same code for flight, you could have a FlyingComponent, which is composition. I've been going all in on composition and it's amazing…

Entity Component System: https://en.wikipedia.org/wiki/Entity_component_system

Yeah, but all current languages still have to wrangle ECS into an inheritance-first architecture: `class` etc.

Would be nice if something like Swift's "Protocols" could be used in a more dynamic way, at the code level.

Re: When did people favor composition over inheritance?

#146

One of the lesser known features in Kotlin is interface delegation. This lets you get away with doing multi class inheritance via composition of a class with a delegate. This kind of blurs the boundaries between inheritance and composition in a useful way. class Foo(internal val _list: MutableList =mutableListOf()): MutableList by _list { ... } Here Foo has a _list property that it delegates the implementation of lis…

That idea is first visible in OOP systems like COM, which depending on the language, or to use a more recent term from WinRT (language projection), exposes that capability.

Since COM only allows for interface inheritance, unlike SOM from OS/2 which also did classes, the way to avoid doing from scratch all members, is to compose and delegate all unmodified methods, while implementing only the new ones in an extended interface.

MFC, ATL, VB and Delphi provided some mechanisms to make this easier, naturally not at the same level as easyness done by Kotlin.

By the way, the same concept is available in Groovy, with @Delegate annotation.

Re: When did people favor composition over inheritance?

#147
post #54

Earlier quoted context omitted.

> at least it is largely passed us now What does this mean? There doesn't seem to be a popular alternative to C++ yet, unfortunately.

Aside from game dev, Rust is being used in quite a lot of green field work where C++ would have otherwise been used. Game dev world still has tons of C++, but also plenty of C#, I guess. Agreed that it’s not really behind us though. Even if Rust gets used for 100% of C++’s typical domains going forward (and it’s a bit more complicated than that), there’s tens? hundreds? of millions (or maybe billions?) of lines of wo…

Rust depends on C++, until people cut their compilers lose from LLVM, GCC, and other C++ based runtimes, it is going to stay with us for a very long time.

That includes industry standards like POSIX and Khronos, CUDA, Hip and SYCL, MPI and OpenMP, that mostly acknowledge C and C++ on their definition.

Re: When did people favor composition over inheritance?

#148

One of the lesser known features in Kotlin is interface delegation. This lets you get away with doing multi class inheritance via composition of a class with a delegate. This kind of blurs the boundaries between inheritance and composition in a useful way. class Foo(internal val _list: MutableList =mutableListOf()): MutableList by _list { ... } Here Foo has a _list property that it delegates the implementation of lis…

I really like the idea of role based programming / mixins. I think it does not get enough attention.

[1]I know only of some programming languages that even call it roles.

To be honest I always get confused by the difference between interfaces and roles. For me it was always something like an interface/behavior that can be mixed in at runtime.

[1]https://docs.raku.org/language/objects#Roles

Re: When did people favor composition over inheritance?

#149
post #45

Earlier quoted context omitted.

This is only the case when the language does not distinguish between methods that can be overridden versus those that cannot. C++ gives you the keyword "virtual" to put in front of each member function that you want to opt into this behavior, and in my experience people tend to give it some thought on which should be virtual. So I rarely have this issue in C++. But in languages like Python where everything is overrid…

The virtual keyword in c++ is more of a compiler optimization and less of a design decision. C++ doesn't want everyone paying the overhead of virtual function calls like other languages

It still funcions as a design optimization even though that isn't the reason for it.

Re: When did people favor composition over inheritance?

#150
post #70

Each has its place. There's some things that inheritance makes possible, and some things that are best handled by composition. I use both, quite frequently. It Depends™. Composition can add a lot of complexity to a design, and give bugs a lot more corners to hide in, but inheritance can be such a clumsy tool, that it just shouldn't be used for some tasks. That goes for almost everything in software. Becoming zealous…

I dunno. It's easy to say, "there are trade-offs, it depends" any time two things are compared, and it's never entirely untrue. However, sometimes one option is just generally worse than the other. I'm not saying it's malpractice to use inheritance or anything, but it's a tool I definitely hesitate to reach for. Go and Rust removed inheritance entirely, and I'd say those languages are better-off without it.

Yeah, it's another thought-terminating cliche, and it's on my list!

https://h2.jaguarpaw.co.uk/posts/thought-terminating-cliches...

Post reply on HN