1

One major reason for using composition over inheritance is, that inheritance leads to higher coupling. How is that? In both cases members are exposed to the subclass (in case of inheritance) or wrapper class (in the case of composition). Compare the following modifications for the base class for inheritance (AnimalInheritance) and composition (AnimalCompositionInterface):

  • modifying the method name of #makeSound
  • adding parameters of #makeSound
  • changing the return type of #makeSound

In all cases the same refactorings have to be made in the subclasses (DogInheritance/DogComposition) or within the main() method, regardless if we're dealing with inheritance or composition. So why is inheritance considered high coupling?

//INHERITANCE EXAMPLE
abstract class AnimalInheritance {
    public abstract int makeSound();
}

class DogInheritance extends AnimalInheritance {
    @Override
    public int makeSound() {
        return 0;
    }
}


//INHERITANCE TURNED INTO COMPOSITION
interface AnimalCompositionInterface {
    public int makeSound();
}

class AnimalComposition implements AnimalCompositionInterface{
    @Override
    public int makeSound() {
        return 0;
    }
}

class DogComposition implements AnimalCompositionInterface{

    AnimalCompositionInterface animal;

    @Override
    public int makeSound() {
        return animal.makeSound();
    }
}


public static void main(String[] args) {
    DogInheritance dogInheritance = new DogInheritance();
    int dogInheritanceSound = dogInheritance.makeSound();

    DogComposition dogComposition = new DogComposition();
    int dogCompositionSound = dogComposition.makeSound();

}
11
  • 2
    You may be interested in Does inheritance increase coupling? from Stackoverflow in 2015.
    – Doc Brown
    Commented Oct 22, 2023 at 20:52
  • I updated my post with an example.
    – cobby
    Commented Oct 22, 2023 at 22:08
  • Coupling is not about being able to see internal details. It's about whether or not a change in one class propagates to the necessity for change in another. Commented Oct 23, 2023 at 6:25
  • @KilianFoth If you go by that definition, inheritance does not introduce any coupling at all, given one abides by the open-closed principle. Commented Oct 23, 2023 at 16:55
  • @MartinMaat: what on earth makes you think that?
    – Doc Brown
    Commented Oct 23, 2023 at 18:09

5 Answers 5

6

It doesn’t have to.

The difference isn’t the required amount of coupling. It’s that with composition the coupling (delegation) is explicit. You have to write out what you’re delegating. With inheritance it’s implicit.

Inheritance is easier to write while composition is easier to read.

But these problems are small compared to the big problem. When reading through a procedure that lives in an inheritance stack you find yourself bouncing up and down levels of inheritance and getting lost. It’s called the yo-yo problem.

You don’t get that problem with composition for one simple reason. You can’t sneak inside your parent. You have to access what you’re composed of through it’s normal public interface like anything else would.

Which means you have the right to expect to work with an abstraction. Something that makes sense even if you don’t look inside. If you don’t have to look inside, no yo-yo.

3
  • So why is explicit better than implicit coupling? In the end I will have to make the same refactorings. In the case of inheritance I will need to adapt the subclasses when the base class is changed, in the case of composition i will need to adapt all classes that implement the interface (the interface basically represents the parent-class) - compare the example in my OP. Also, I do know the YoYo problem, but imo i'm shifting the problem of weaving in/out of parent/sub to weaving in/out to delegeting/delegated class?
    – cobby
    Commented Oct 22, 2023 at 23:48
  • The interface represents the public API. If you need to make a lot of changes to the interface, it can imply that the interface is not well defined. Composing functionality from multiple well defined (small) pieces reduces the need of refactoring (only if the public API changes), and in Java's case, interface default methods can help fill in functionality to reduce refactorings further.
    – Kayaman
    Commented Oct 23, 2023 at 8:29
  • Just to clarify, the coupling is implicit, since base and subclasses share öublic/protected members?
    – cobby
    Commented Oct 24, 2023 at 0:01
3

Coupling between classes can only be lowered by using interfaces or abstract classes. The idea is to swap out the implementation of one of these, and the other classes that depend upon those interfaces will continue to work as before. Therefore, if class A uses an interface B, and you have classes C and D as implementations of B, A is not coupled directly to C or D. If A contains C or a D as concrete implementations, than A is coupled to C or D.

Following the same logic. If A inherits B, then you can't swap B for another implementation, therefore there's a high coupling between A and B (unbreakable one once you shipped a framework for example).

Therefore...

Composition leads to lower coupling if and only if the composing members are interfaces or abstract classes. Otherwise it is the same coupling. However, even if you do have concrete classes inside a composition, you still have the advantage of being able to maybe extract an interface or abstract class out of these. So if your class uses only interfaces to do its job, then you can swap the implementation of any of those interfaces, and there is no tight coupling between your class and any of the implementations.You can't really do this with inheritance (some tools will consider inheritance as higher coupling because of this, but this is subjective). This is assuming SRP has be followed here. You can still create horribly coupled classes using composition. Simply pulling out some functionality out of your class in another class is not going to lower coupling.

There's 2 basic rules that you can follow to figure out if you should use inheritance or composition.

  1. If class A is a specialization of B, then A inherits B (i.e inheritance). It is like a dog is a specialization of carnivorous animal.
  2. If class A needs something that B can do, or has a B, then B should be a member of A (i.e composition). A dog needs bones to move, but you can't say a dog inherits bone, right?

Your example clearly demonstrates a high coupling with composition. The class DogComposition shouldn't depend on the concrete AnimalComposition. It should depend on the interface AnimalCompositionInterface. To be 100% to the book, the instance of AnimalCompositionInterface should be passed as a parameter in the constructor. That way, if a class AnimalComposition2 comes to be, you can swap AnimalComposition and AnimalComposition2 without modifying DogComposition . Also DogComposition shouldn't implement AnimalCompositionInterface. You basically implemented the facade pattern around AnimalCompositionInterface.

//INHERITANCE TURNED INTO COMPOSITION interface AnimalCompositionInterface { public int makeSound(); }

class AnimalComposition implements AnimalCompositionInterface{
    @Override
    public int makeSound() {
        return 0;
    }
}

class DogComposition{

    AnimalCompositionInterface animal;

    public DogComposition(AnimalCompositionInterface impl) {animal = impl;}


    @Override
    public int makeSound() {
        return animal.makeSound();
    }
}

public static void main(String[] args) {
    DogInheritance dogInheritance = new DogInheritance();
    int dogInheritanceSound = dogInheritance.makeSound();

    DogComposition dogComposition = new DogComposition(new AnimalComposition()); //you can swap implementations here
    int dogCompositionSound = dogComposition.makeSound();

}

The inheritance hierarchy is clearly coupled because the derived class uses the abstract's class public method to do things. So DogInheritance is coupled to AnimalInheritance. You can't swap AnimalInheritance for some other class with a different method to override.

EDIT #2

Since you updated the example to use the code above, the idea is basically that your composition is now coupled to the interface AnimalCompositionInterface and not to the concrete implementation of AnimalComposition. Since interfaces do not have actual implementation, this is considered acceptable coupling because you are essentially coupling to nothing. Or that you do not directly couple DogCompsotion to AnimalComposition, but use an intermediary instead. Interface coupling is needed to ensure type safety. Your example is a bit too simple to properly see the merit in using composition over inheritance. The problems discussed here occur in deep inheritance hierarchies with many public and protected methods.

2
  • Please refer to the example
    – cobby
    Commented Oct 22, 2023 at 22:08
  • @cobby see edit.
    – Ccm
    Commented Oct 23, 2023 at 5:04
2

Your two examples both use the general concept of interface inheritance - which can be achieved in Java syntactically either by abstract classes with no protected members, or equivalently, by Java interfaces.

And when you combine "composition with interface inheritance", it is no surprise you get the same amount of coupling in both cases.

If you want less coupling, use composition without a common Java interface - and voila, the necessity of changing the public method names of your composite when your "parts" change their public method names vanishes - which demonstrates less coupling.

3
  • with the drawback of loosing polymorphism, the main reason for inheriting at all. I don't see how you can turn inheritance into composition without an interface
    – cobby
    Commented Oct 23, 2023 at 9:17
  • @cobby: well, you seem to believe "composition will automatically lead to less coupling than inheritance" - but where in your question is the reference where you found this, so we can see if that reference is wrong or just your understanding of it? Automatically replacing one concept of interface inheritance by another one schematically will not change any coupling in your code, There are, however, lots of cases where implementation inheritance can be replaced by composition, and that will usually lower the coupling.
    – Doc Brown
    Commented Oct 23, 2023 at 18:22
  • 1
    Yes, my intention was to change an inheritance based design into a composition based design keeping polymorphic behaviour in order to reduce coupling. But you're right that explicit coupling doesn't change a bit in this case (otherwise it wouldn't be a functionally equal design). The difference probably lies in the implicit coupling which is only part of inheritance leading to more of behavioral instead of structural coupling, e.g. fragile base class problem
    – cobby
    Commented Oct 23, 2023 at 23:43
2

You've got some good answers here, but I think a concrete example could help:

abstract class DatabaseObject {
    Connection getConnection() {
        // database stuff
    }

    abstract void load();

    abstract void save();
}

interface Person {
    String getName();
}

abstract class Employee implements Person extends DatabaseObject {
    private String name;

    String getName(){
        return name;
    }

    void load() {
        Connection connection = getConnection();
        // get name etc. from DB ...
    }

    void save() {
        Connection connection = getConnection();
        // write stuff to DB...
    }
}

abstract class Student implements Person extends DatabaseObject {
    private String name;

    String getName(){
        return name;
    }

    void load() {
        Connection connection = getConnection();
        // get name etc. from DB ...
    }

    void save() {
        Connection connection = getConnection();
        // write stuff to DB...
    }
}

You might notice that there's a fair of code duplication here. That's not exactly on topic but it does play into this.

I used to see this kind of thing a fair amount. It 'works' but creates a lot of challenges as a system evolves. For example, let's say now that you have to support an alternate way of saving things to JSON files. What do we change?

We can introduce a new method called saveToFile or something and add that to the Employee and Student classes. But that doesn't really help us becaause, Bob, the grumpy UI dev on the team is not going to add if statements in order to call your new method whenever the JSON save option is to be used. Your team insists that the JSON save functionality must happen in a save method. What are your options? If you want to stick with inheritance, you will need to introduce a new interface and a Json class. But you need to also add JsonStudent and JsonEmployee classes. The reason you have to do this is because your Student and Employee classes are coupled to the Database class through inheritance. And then guess what, you now get a requirement for yet another type of persistence. Do you see where this is going and why it's a problem?

To avoid this kind of coupling, you can introduce composition:

interface Person {
    String getName();
    void save();
}

interface Persistence {
    Record load();
    void save(Record record);
}


abstract class Employee implements Person {
    private String name;

    Employee(Persistence) {
        // get name etc. Perstence ...
    }

    String getName(){
        return name;
    }

    void save();
}

abstract class Student implements Person {
    private String name;

    Employee(Persistence) {
        // get name etc. Perstence ...
    }

    String getName(){
        return name;
    }

    void save();
}

Now the Student and Employee types are decoupled from the database persistence. You can have any number of ways of persisting these objects without and changes to Student and Employee types. You might notice that this is also a way to do dependency injection (DI) and I would argue that composition is a prerequisite for an effective DI strategy.

This is obviously a trivially simple example, but this kind of issue crops up again and again when you attempt to inherit implementation details. instead of a nice modular system where you have a small number of pieces that you can put together in many different combinations, you end up with a combinatorial explosion of class definitions. One place where you can see how this tends to play out is in the Swing API.

2
  • Good example, basically it's about the cost of exchanging the base class of the subclasses, which is achieved through additional subclassing. However it's not the tight coupling between base and sub class that forces me to create new subclasses, it's rather how inheritance works compared to composition, right?
    – cobby
    Commented Oct 24, 2023 at 0:36
  • @cobby I think that's a distinction without a difference. The way inheritance works tends to result in coupling. If we take the general definition of coupling from wikipedia: "coupling is the degree of interdependence between software modules" I would argue is a pretty good description of how the business classes relate to the Database class in my first example. Is your question mainly about the definition of coupling?
    – JimmyJames
    Commented Oct 24, 2023 at 14:13
1

I find these "inheritance isn't so bad" posts fascinating, because "prefer composition" worked so well that new devs don't even know how inheritance was used in the 90s. A typical example is:

class MySaveButton extends StdLibButton with ClickListenerInterface {
  def onClick(): Unit = ??? // Saving business logic
}

This pattern lets you put a MySaveButton anywhere a StdLibButton is expected. The implementation of onClick can directly use any StdLibButton methods like method() instead of button.method(). The code was often more concise than when using composition. So what's the problem?

Your MySaveButton is in the StdLibButton inheritance hierarchy, which you have no control over, and it cannot inherit from any other class. Being tied so strongly to StdLibButton is usually fine at first, but limits your options down the road. You end up wanting to violate other design principles such as putting business logic in presentation layers, because you can't create a separate business logic inheritance hierarchy. It's also more difficult for the standard library developers, because they have to anticipate derived classes potentially overriding behavior in unexpected ways.

Modern development practice tends to do more inheritance from interfaces, which isn't as problematic as classes because you can implement multiple interfaces. Additionally, modern languages have added features such as traits and multiple inheritance that make inheritance less problematic. Modern libraries tend to make their classes final so people can't inherit from them even if they wanted to.

All those factors make the remaining uses of inheritance not so bad, but it took a while to get to that point.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.