Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login

This doesn't inhherently have anything to do with inheritance. Delegation is the compositional solution to this problem and some languages do have built in sugar for that. It usually looks something like:

    class F150(@delegate private val underlying: Car) { ... }
    class F150(private val underlying: Car) : Car by Underlying { ... }
    // etc


sort by: page size:

That is very much not inheritance. It’s literally just sugar for delegation: calls which delegate to embedded structs execute in the embedded struct’s context.

And the embedded struct has to be part of the struct definition, it can’t be snuck in by reopening the class and include-ing new bits.


That's not inheritance, it's just syntax sugar that lets a struct delegate method calls to one of its members. In other words:

    struct Foo {}

    func (f *Foo) baz() { println("Foo.baz()") }
    func (f *Foo) qux() { f.baz() }
Given the above, `struct Bar { Foo }` is the same as:

    struct Bar { Foo Foo } // field called `Foo` of type `Foo`

    func (b Bar) baz() { b.Foo.baz() }
    func (b Bar) qux() { b.Foo.qux() }
Since it's just syntax sugar and not inheritance, we can't put a `Bar` in a list of `Foo`s nor can we pass a `Bar` into a function that expects a `Foo`. It also means that if Bar overrides its `baz()` method like so:

    func (b Bar) baz() { println("Bar.baz()") }
that calling `Bar.qux()` will still print "Foo.baz" and not "Bar.baz" (most languages with inheritance will print "Bar.baz", which is to say methods are virtual by default).

This is the most controversial feature of Kotlin.

The delegation is better than inheritance because you don't get all the methods from the base class if you don't need them.

At my company, we have banned it, because if you add a method to Bar the method is automatically added to Foo which makes the delegation as fragile as the inheritance.


This article seems to be criticizing _automatic delegation_, not _composition_. Delegation is not necessary for every (or even most) instance of composition. Beyond that, languages provide more narrowly-tailored forms of delegation than "forward every method that the underlying object supports".

Furthermore, the criticism of inheritance that the article sets up isn't the one that "composition over inheritance" is meant to address. Languages like Java with access modifiers (public/private/protected) don't have the accidental namespace collision issue the writer demonstrates, and yet "composition over inheritance" is a popular concept in those languages, too. The problem with inheritance is that the Liskov substitution principle is very easy to accidentally violate, and those violations can lead to a brittle and inflexible design over time. (See https://en.wikipedia.org/wiki/Circle-ellipse_problem)

Finally, I don't understand the relevance of the "sororicide" metaphor, nor does the article describe anything identifiable as an antipattern.


Ah, ok, I mixed up you with continuational.

For this context, what I'm saying is that if you don't need to extract an apple from a list of fruits, then you can do it safely with inheritance, so whether using inheritance or delegation is irrelevant. Isn't it?


From Design Patterns (first edition):

> Delegation is a way of making composition as powerful for reuse as inheritance [Lie86,JZ91]. In delegation, two objects are involved in handling a request: a receiving object delegates operations to its delegate. This is analogous to subclasses deferring requests to superclasses. But with inheritance, an inherited operation can always refer to the receiving object through the "this" member variable in C++ and "self" in Smalltalk. To achieve the same effect without delegation, the receiver passes itself to the delegate to let the delegated operation refer to the receiver.

Sounds like they're perfectly clear and correct to me. As the author of the post points out the GoF don't actually show examples of delegation in the book and I don't recall them using the term much, so I don't see how they're perpetrating any misinformation here.

> In JS we can assign a prototype: the equvalent of the parent slot in Self.

No, .prototype is not equivalent to a parent slot in Self. It's closer to a traits object, I think. The unstandardized __proto__ property is equivalent to a parent slot in JS.

> Yeah, Design Patterns showed examples in Java. And Java can’t do delegation. So is that a valid argument for rewriting the meaning of a term?

Design Patterns is older than Java. Maybe later editions use it, but the original version used C++ and Smalltalk.

Otherwise, good troll post, original author. I'm sure you'll get lots of traffic and people noticing your book. Well played!


I've always wondered why (implementation) inheritance is a first class construct while delegation has to be painstakingly implemented manually.

They can be modeled as inheritance or as composition plus delegation, but that's not saying much since inheritance itself can be modelled as composition plus delegation.


    > This is very useful when you want polymorphism.
    > Writing those delegators is a pain in the butt.
What's a scenario where you'd be exposing a large number of delegators? Reading this made me think - maybe your class hierarchy needs to be abstracted more deliberately into structs-with-interfaces vs domain logic classes. (It's more likely I just haven't thought about the kinds of problems you are thinking of.)

In fact, depending on whether the delegated methods depend on (protected) super-class state, this is probably better served with composition than inheritance.

Isn't delegating to the parent object prototypical inheritance?

That's also been my experience. I've also seen programmers use composition and delegation solely to avoid inheritance (in a language that makes it natural), and the result is often less maintainable than a design that just uses inheritance.

Two other random thoughts...

It amazes me that despite the long-ago realization to favor composition over inheritance, that major oop langs only provide direct support for (implementation) inheritance of methods and not delegation. I suppose "delegation in context" has become be purview of DI/IoC add-ons - but direct language support would be nice.

While confounded with OOP by many, Generics are probably best treated as orthogonal -- if they had been the primary focus (including higher-kinded types/modules) rather than inheritance, that would have been nice...


Kotlin's delegation feature is useful sometimes, but OOP inheritance still crops up more often.

One reason is that inheritance is strictly more powerful. When you delegate (compose), you can't modify the inner working of the object you delegated to, you can only wrap it. Often that's not sufficient. You want to actually customize the implementation in some way. OOP's inheritance is ideal for this, because the superclass can clearly define chunks of functionality that subclasses are allowed to either (a) replace entirely or (b) wrap or (c) provide (abstract methods).

This is a very flexible approach. With mere delegation you can only wrap, and only for method calls that originate outside the object, and only for public methods.

It's also clean. The distinction between what you must implement, what you can implement and what is for the user is defined in the type system and enforced by the compiler. Additionally you separate the interface for customizations from the interface for users (protected vs public), which keeps documentation and auto complete properly focused.

When combined with dependency injection and code generation it also allows third party frameworks and libraries to enhance the code you write in various ways, that remain mostly tucked out of the way but can be revealed in an IDE in an instant (and a good IDE will show visual hints that there are such customizations in effect).

The final advantage is that in some languages the implementation is highly optimized. On HN you'd be forgiven for thinking nobody writes virtual method calls since 20 years ago but in reality virtual method dispatch is so common in real programs that it's heavily optimized by both CPUs (sophisticated indirect branch caching) and language runtimes (PICs in fast VMs like Hotspot or V8). Composition is less well optimized.

Inheritance gets an overly bad rap on Hacker News, despite how prevalent it is. I think this is because it's so flexible you can easily make a mess with it, and some people have. Also the decision of Java to make all classes and methods open by default means overriding is often used as a hacky way to hot-patch libraries in the field, rather than as part of any principled overall design. Kotlin reverses that decision to some extent and so classes and methods are all final by default, unless a compiler plugin overrides that.

I also suspect the move to web development has had this effect, because in most classical OOP toolkits you are allowed or even encouraged to extend the UI by subclassing pre-existing node types. So you can make a custom button by subclassing Button or whatever. HTML is implemented using classical OOP designs, but mostly for implementation reasons that isn't exposed to the web developer who instead is expected to customize the controls purely through CSS and setting properties. If you want to make a custom button widget, well that's too bad, better learn React or something similar. So people just aren't exposed to the situations where OOP is useful because browsers weren't designed for UI, and the memory of why it was originally developed atrophies.

And some of it is just memeing.

OK, now tell me why I'm wrong.


That's a good idea. And as that's a common pattern, you could allow the programmer to specify that the compiler should automatically implement this delegation. You could call this feature something like, I don't know, "multiple inheritance".

Delegation can model inheritance or other OOP semantics. If you squint your eyes delegation and lexical scope nesting kinda resemble each other.

that sounds a lot like sharing code by inheritance as opposed to composition. which is unrelated to dispatching by the type of the first (or implicit) argument.

i personally would prefer to code to interfaces and keep hierarchy separate from behaviour


I think this means doing composition/delegation, which the author of TFA calls "basically giving up on implementation inheritance."
next

Legal | privacy