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

In Raku those things are also separate.

1. Polymorphism

    class Example {
      method be-nice () {
        'be nice'
      }
    }

    class Bill-and-Ted is Example {
      method be-nice () {
        'be excellent to each other'
      }
    }
2. Composition

    role Salaried {
      has $.salary;
    }

    role Employee {
      has $.title;
    }

    class Manager does Employee does Salaried {}
3. Delegation

    class Price {
      has $.cost;
    }

    class Product {
      has Price $.inner handles( 'price' => 'cost' );
    }
Any call to the `.price()` method on `Product` will delegate to the `.cost()` method on the inner object.


sort by: page size:

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

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

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.

In OO languages, inheritance gets the most bang for the buck when used for polymorphism.

All code reuse can be expressed with composition and delegation, bringing more flexibility, testability, cohesion, decoupling, etc, etc, etc.


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

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


Ah I see. What you say is not quite true though, ad-hoc polymorphism (traits) provides the same functionality for non-OOP languages.

> Until recently non-OO languages had very little support for delegation


Right, but the point is you can avoid polymorphism within a given implementation but still keep it at a higher level.

Heck, even codebases used in trading can use polymorphism for very high level interfaces (or async tasks) but hot code paths don't use it.


> You're describing a fairly narrow subset of subclassing; notably one that is (as you point out) almost equivalent to composition.

I'm not sure why you say this is a narrow subset. I'm describing a way of thinking about inheritance (inheritance is like composition + automatic delegation). That way of thinking can be applied to any subclassing operation, and I think it's instructional to do so. It can help you see what's a good idea and what's not.

Let's run down the list. Assume you have a class A with a method foo() and class B with method bar().

The equivalent* of "A extends B" is:

    function A() {
      this._b = new B();              // equivalent* to calling superclass constructor
    }
    A.prototype.bar = function() {
      return this._b.bar();           // manually delegate
    }
Now let's say A accesses its superclass's private variables in order to do something. The equivalent* is:

    A.prototype.foo = function() {
      return this._b._privateVar * 2;     // obviously bad, don't do that!
    }
    
You wouldn't access the private variables of an object you compose; it's obviously bad form. Don't do it when you inherit, either.

Now let's talk overriding the superclass's methods. There are several different ways you could do that. The equivalents* are:

    // Replace superclass method
    A.prototype.bar = function() {
      return "my own thing";            // obviously fine
    }
    
    // Replace superclass method and access superclass state directly
    A.prototype.bar = function() {
      return this._b._privateVar * 2;   // obviously bad, don't do that!
    }
    
    // Extend superclass method
    A.prototype.bar = function() {
      var result = this._b.bar();       // equivalent* to calling superclass method
      return result * 2;                // perfectly okay
    }

    // Extend superclass method and access superclass state directly
    A.prototype.bar = function() {
      var result = this._b.bar();       // equivalent* to calling superclass method
      return result * this._b._privateVar;   // obviously bad, don't do that!
    }
    
Don't access the private variables of your superclass (or objects you compose) and you'll be fine. Sure, you'll be in trouble if the superclass changes the semantics of the parent method, but that's true of all functions everywhere. If the semantics of a function you're using changes, your code probably just broke. It doesn't matter if the function is defined in a superclass or not.

The one thing that's unique to inheritance is the idea of semi-private ("protected") methods that are only visible to subclasses. I agree that they're something to be used sparingly, but they're no different than any other superclass method in how they should be used and overridden. It's a moot point, though, because JS doesn't have them.

*Not exactly equivalent, but close enough for these examples.


The first step in separating polymorphism and inheritance is understanding that there are several forms of polymorphism. The one that gets lumped in with inheritance is subtype polymorphism, but there's at least two other sorts (not an expert, there may be more?): parametric polymorphism ("generics") and ad-hoc polymorphism (e.g. rust traits, haskell typeclasses). All of these achieve one form of code sharing, which is that, in one way or another, code that only depends on your external interface can be reused with any implementation of that external interface.

The other place you want code sharing is the implementation of that external interface. Inheritance is one well-known way to do this, but composition/delegation is another strategy to achieve the same thing. Golang's embedded structs are a pretty streamlined (but fairly limited) way of achieving this, and e.g. Kotlin has explicit delegation: `class Derived(b: Base) : Base by b`. Languages like e.g. Java (where classes are open by default) push you towards inheritance, while languages like Kotlin (where classes are final by default, and where you have explicit support for delegation) push you towards composition.

A whole lot of confusion comes from most mainstream OOP languages conflating inheritance and subtype polymorphism.


I can think of one. For Java at least.

Imagine that I have a class that has 15 methods -- 14 are a perfect fit as is, but 1 of them needs to be completely overwritten.

If I were to do inheritance, I would do `extends` on the class, and then `@Override` the offending method. Problem solved.

If I were to do composition, I could use the Delegation Pattern from the Gang of Four, but that would require me to write 14 do-nothing methods just to be able to override the one.

In general, composition has more use than inheritance. However, inheritance has a non-zero number of use cases where it is a better choice than composition.


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.


If you are calling a method on a delegated object then you know it's one of the delegated objects rather than a method on the base class. It makes it really easy to see which bits vary by between the delegated/sub objects and which bits are the same for all of them. With inheritance you have to check through every single sub-class to see if they've overridden a method.

Code paths where an overridden method in a subclass calls back into a method on the base class are much clearer too, as they have to be explicitly passed in as parameters, the sub-class doesn't have access to any of the parent class's state or methods by default.


I think it's important to think of these features in terms of their smallest possible descriptions. One mistake with Java, I think, is that "inheritance" is how you do both polymorphism and composition (I know you can do either without inheritance, but it's especially a pain to do composition without).

With Go, polymorphism is done with interfaces. Composition is done with embedded fields (and the very convenient method promotion).


A super quick, simple illustration of the difference, which comes down to the fact that delegation is differential, whereas composition is complete:

https://gist.github.com/getify/9895188

For a bit more "real world" of a scenario, you can also compare the `LoginController` / `AuthController` mechanism here, first shown with inheritance+composition, and then shown with simpler OLOO-style delegation:

https://github.com/getify/You-Dont-Know-JS/blob/master/this%...


What about polymorphism ?

How does one deal with low coupling? For instance, in PHP I would have used interfaces to decouple collaborators.


I said nowhere in that article method polymorphism is implemented.

Well, not quite. You said that it isn't OOP without method polymorphism. So no need to fly off the handle and accuse others of misreading something you didn't write.


Multiple inheritance of code is possible with interfaces and default methods.

But that's what polymorphism does.

Somewhere deep in the code is calling a.foo(), but when you pass a subclass of A that overrides foo(), then this code "magically" calls that new implementation.

This is where specialization shines and no other paradigm allows this so elegantly and so simply.

next

Legal | privacy