Prefer compositon over inheritance

on waitingforcode.com

Prefer compositon over inheritance

You're still doing Java/C#/JavaScript/Python/PHP... and need a wind of change? I was like that 4 years ago. I changed then to the data engineering field and it solved my existential problems :) If you want to follow my path, I prepared a course that will help you with that! Join the class!
When we discover object-oriented programming after passing by functional programming, inheritance seems to be revolutionary technique. However, it also can be misused, usually in the case when it should be replaced by composition.

In this article we'll discover another good programming practice, prefer composition over inheritance. At the begin we'll define two elements composing that: composition and inheritance. After that we'll explain shortly why "prefer composition over inheritance" shouldn't be thought as empty slogan and used everywhere, every time. At the end, exactly as in the case of the article about prefer interface over implementation, we'll present some of composition advantages.

Composition and inheritance

The meaning of "composition" word in programming is not very different from the meaning from the real world. In fact both describe one complex element composed by another simplest elements. A good example of composed object can be a house, constructed by some of "simple objects" as bricks, tile or foundations. As you can see, this complex object has another objects. It's also known as "has-a" relationship. In programming world, composition is expressed by object fields.

In the another side, the principle of inheritance is different. If we would to find a comparison with real world, we could also take a house as an example. A house can be constructed with different materials. Suppose that we build one floor brick house. Our neighbor also wants to construct brick house. However, he wants that his house has 2 floors. So, he'll take some common characteristics of ours (brick) and replace only one different characteristic (2 floors instead of 1). This "taking" process is an inheritance. In programming it's the same thing. One object can extends directly another one and override some of inherited behaviors.

To undestand this difference directly in code level, let's translate the implementation of 2 floor brick house into class using inheritance and composition:

// inheritance sample
public class BrickHouse {
  public void createFloors() {
    System.out.println("Making 1 floor");
  }
}

public class BrickHouseWithFloor extends BrickHouse {
  @Override
  public void createFloors() {
    System.out.println("Making 2 floors");
  }
}

// composition sample
public class BrickHouse {
  // can be FloorOneLevel, FloorTwoLevels etc.
  private Floor floor;
  
  public void createFloors() {
    floor.createFloor();
  }
}

As you can see, both snippets create reusable code. But the difference is that in inheritance, one class adopts the behavior of super class and override just some of them. In the case of composition, one class holds the references to another classes.

Compose rather than inherit ?

Basically, both techniques have theirs reasons to be. However sometimes they're used in not suitable situations, ie. composition is used in typical situations for inheritance, and inversely. Some people use inheritance as a basic way to build a reusable code. But the main purpose of inheritance is polymorphism, ie. the ability to both share and override behavior with super class.

In Java, very popular examples of inheritance misuse are java.util.Stack and java.util.Properties classes. The first one extends directly Vector class and the second Hashtable one. The problem here is that both super classes are not fully adapted to the sub classes needs. In the case of Stack, we could need only 3 methods to manipulate LIFO stack: push, pop and peek. All elements could be hold in private field representing List implementation. But instead of that, Stack inherits all of not really useful Vector's methods, as all elements removing or to array converting. In additionally, Stack can use Vector's methods which allow to insert an element at specified position (insertElementAt(E obj, int index)). It clearly violates the rules of ordered LIFO stack where each new element shouldn't be placed before older ones. Thanks to that we can simply deduce that Stack is not a ("is-a" relationship) Vector but, eventually could contain one ("has-a" relationship).

This example should help to understand that inheritance is not bad and composition appropriate solution every time. It really depends on design goals.

Composition benefits

We shouldn't discredit inheritance and composition. But we should use them regarding to the situation and benefits which they can bring. In the case of composition, the first advantage is testability. Imagine the situation where your object uses the features of two another objects. To stay in construction industry world, let's say that we have an object representing secured brick house:

public class SecuredBrickHouse {

}

// Here we misuse inheritance to only make a code "reusable". There're 
// no polymorphism (NormalBrickHouse should
// extend BrickHouse)
public class NormalBrickHouse extends SecuredBrickHouse {

}

Now, to test this class, we'll probably need to submit all security configuration - even if tested object doesn't care about it ! To make this code easier to test with composition, we could rewrite it like this:

public class BrickHouse {
  private Caretaker caretaker;
}

To test this class, we can configure Caretaker (security configuration from previous paragraph). But we don't need to do that when we want to test non-secured methods. Even more, we could test this object against different Caretaker implementations, for example less and more restrictive. It'll be more complicated with previously presented code (SecuredBrickHouse) which will need the definition of new objects as LessSecuredBrickHouse and MoreSecuredBrickHouse...

Composition technique facilitates objects modeling in ORM systems. For example, to illustrate relations between two tables, we can simply use foreign-key relationship and famous JPA's @OneToMany, @ManyToOne annotations to indicate joined entities. We could also try to model that with inheritance but it could produce monsters composed by hacky workarounds. To understand that better, take a look on below cases, the first one modeled with composition and the second one with inheritance:

// composition modeling
public class BrickHouse {
  @ManyToOne
  @JoinColumn(name="floor_id")
  private Floor floor
}

public class Floor {
  @OneToMany(mappedBy = "floor")
  private List houses;
}

// inheritance case
public class BrickHouseTwoFloors {
  // some of BrickHouse and Floor 
  // properties adopted to 2 floor house
}
public class BrickHouseThreeFloors {
  // some of BrickHouse and Floor 
  // properties adopted to 3 floor house
}
// ... and so on. Each new object will probably 
// require some changes at the database level too.

In the cases of several programming languages, as Java, multiple inheritance is not permitted. It means that one object can extend directly one other object. Because of this limitation, we can easily produce very deep object graphs, like that:

public class BrickHouse {
  
}

public class BrickPaintedHouse extends BrickHouse {
  protected String color;
}

public class BrickFloorPaintedHouse extends BrickPaintedHouse {
  protected int floors;
}

public class BrickPaintedHouseWithGarden extends BrickPaintedHouse {
  protected String gardenName;
  protected int gardenArea;
}

public class BrickFloorPaintedHouseWithGarden extends BrickFloorPaintedHouse {
  protected String gardenName;
  protected int gardenArea;
}

As you can see, this code will become not maintainable very quickly. Instead of preferring inheritance here, we could think about composition as a solution for multiple inheritance constraint. Refactored code could look like:

public class BrickHouse {
  private Color color;
  private Floor floor;
  private Garden garden;
}

public class Color {
  private String name;
}

public class Floor {
  private int floors;
}

public class Garden {
  private String gardenName;
  private int gardenArea;
}

Previous sample brings us another advantage of composition, its use in design patterns. Considered as good coding practices, design patterns helps to write more maintainable code. And composition participates on several of them. The first design pattern using it is Strategy. To simplify its definition, its main purpose consists on adopting code behavior to specific runtime environment. For example, imagine the situation when you have one JavaScript method which is supported by PC and another one, which makes exactly the same things, but is only supported by smartphones. This is a coded example of this situation:

var strategies = {
  pc: function() {
    return pcMethod();
  },
  smartphone: function() {
    return smartphoneMethod();
  }
}

// farther in the code - the code is considered here as execution context
var com.waitingforcode.ExecutionContext = function() {
  
  var strategyKey = 'smartphone';
  if (pcContext()) {
    strategyKey = 'pc';
  }
  this.strategyMethod = strategies[strategyKey];
}

As you can see, we don't need to create two executions contexts, one for PC and second for smartphone. Instead of it we can use a dynamic method resolving based on external criteria as device. And this dynamically resolved method is a member of elements composing common execution context. Another design pattern using composition is Decorator. In this situation, we don't need to extend a class to add additional functionality. In the place of that we use a kind of decoration chain in which all element specifies some behavior to decorated object. You can understand it better by watching at below example of coffee creation:

// abstract decorator - note the presence of coffee field
abstract class CoffeeDecorator extends Coffee{
  protected Coffee coffee;
  public CoffeeDecorator(final Coffee coffee){
    this.coffee=coffee;
  }

  @Override
  public double getPrice(){
    return this.coffee.getPrice();
  }

  @Override
  public int makeMoreCandied(){
    return this.coffee.makeMoreCandied();
  }

}

// concrete decorators
class MilkDecorator extends CoffeeDecorator{
  public MilkDecorator(final Coffee coffee){
    super(coffee);
  }

  @Override
  public double getPrice(){
    return super.getPrice()+1d;
  }

  @Override
  public int makeMoreCandied(){
    return super.makeMoreCandied()+1;
  }
}

class SugarDecorator extends CoffeeDecorator{
  public SugarDecorator(final Coffee coffee){
    super(coffee);
  }

  @Override
  public double getPrice(){
    return super.getPrice()+3d;
  }

  @Override
  public int makeMoreCandied(){
    return super.makeMoreCandied()+1;
  }

}



class ChocolateDecorator extends CoffeeDecorator{
  public ChocolateDecorator(final Coffee coffee){
    super(coffee);
  }

  @Override
  public double getPrice(){
    return super.getPrice()+5d;
  }

  @Override
  public int makeMoreCandied(){
    return super.makeMoreCandied()+1;
  }

}

class BlackChocolateDecorator extends CoffeeDecorator{
  public BlackChocolateDecorator(final Coffee coffee){
    super(coffee);
  }

  @Override
  public double getPrice(){
    return super.getPrice()+5d;
  }

  @Override
  public int makeMoreCandied(){
    return super.makeMoreCandied()-3;
  }
}

public class DecoratorSampleTest{
  @Test
  public void test(){
    final Coffee completeCoffee=new ChocolateDecorator(new SugarDecorator(new MilkDecorator(new BlackCoffee())));
    System.out.println("Candied level is : "+completeCoffee.makeMoreCandied());
    assertEquals(completeCoffee.getPrice(), 11d, 0d);
    final Coffee sugarCoffee=new SugarDecorator(new BlackCoffee());
    assertEquals(sugarCoffee.getPrice(), 5d, 0d);
    final Coffee sugarMilkCoffee=new MilkDecorator(new SugarDecorator(new BlackCoffee()));
    assertEquals(sugarMilkCoffee.getPrice(), 6d, 0d);
    final Coffee sugarBlackChocCoffee=new MilkDecorator(new SugarDecorator(new BlackChocolateDecorator(new BlackCoffee())));
    assertEquals(sugarBlackChocCoffee.makeMoreCandied(), -1);
  }
}

This article shows that preferring composition over inheritance should be used according to program design goals. Inheritance is not a worse solution than composition. It has only different purposes and shouldn't be used as a solution for write reusable code without taking in consideration polymorphism constraints. Because maybe it will be better to create complex object composed by several simplest ones that create complex object extending a lot of unnecessary methods violating the behavior of this object (as in the case of Stack). We also learned that composition has a lot of benefits in the case of tests or objects representing some of persistent data as database rows.

Share on: