Object-Oriented Design – Composition vs Inheritance for Child Classes

coding-stylecompositioninheritanceobject-oriented

According to Why should I prefer composition over inheritance?, I should prefer composition over inheritance. But what if I need to access the interface and class member in generic way? For example, I have parent class Shape and some child classes:

public class Shape{
    private String customId;
    public String getCustomId(){
        return customId;
    }
    public void setCustomId(String aCustomId){
        customId=aCustomId;
    }

    public abstract void draw();
}

public class Circle extends Shape{
    @Override
    public void draw(){
    }
} 

Some codes to use Shape interface

for(Shape s : shapeArray){
    s.draw();
    //access customId
    if(s.getCustomId().equals(...)){
    }
}

...

public class User{
    private Shape shape;
    public void printInfo(){
        System.out.println("User Shape id:"+shape.getCustomId());
    }
}

But it is violating composition over inheritance, so I change the inheritance to composition+interface:

public interface Shape{
    String getCustomId();
    void setCustomId(String aCustomId);
    void draw();
}

public class Circle implements Shape{
    private String customId;
    public String getCustomId(){
        return customId;
    }
    public void setCustomId(String aCustomId){
        customId=aCustomId;
    }

    public void draw(){
    }
}

I found composition is more unmaintainable in this case because:

  1. when I add a new class, I need to copy and paste

    private String customId;
    public String getCustomId(){
        return customId;
    }
    public void setCustomId(String aCustomId){
        customId=aCustomId;
    }
    
  2. If I need to add a new child class member to Shape, I also need to edit all child class to add the class member as well as getter and setter into it

So my question is, do I need "composition over inheritance" if I need to access both the class member and methods in generic way?

Best Answer

"prefer composition over inheritance" is not a braindead rule saying one should avoid inheritance under all circumstances - that would miss the point of that recommendation, and would be nothing but a form of cargo-cult programming.

What the rule actually means is: using inheritance just for reusage is most often the wrong tool - especially when equivalent reusage can be achieved by composition. Same holds when there are parts inherited which are just needed sometimes, or have to be changed at runtime. However, for polymorphism, using inheritance is fine.

In your example, if each Circle is a Shape with a polymorphic draw method, then using inheritance is a perfectly valid option for this part of the class model. If in your specific context each kind of Shape object always, always, always needs an Id, with no exception, then putting the related implementation into the Shape base class is at least a pragmatic solution, not perfect, but still ok.

The situation changes if one needs sometimes shapes with an Id, and sometimes not, making the Id part of the base class implementation is quite questionable:

  • It restricts the reusability and maintainability of the Shape class, because it violates the single responsibility principle - now the Shape has two different responsibilities: providing a generic draw interface and providing identifyability through an Id.

  • Moreover, the setCustomId makes a Shape mutable, which imposes also certain usage restrictions.

  • And as a minor drawback, each Shape object will allocate a small memory block for an additional string reference, required or not.

So if one wants to create a highly reusable shape class, I would heavily recommend to remove the Id part from the Shape completely, and introduce another class like a TaggedShape, which is composed of an Id and a shape:

 class TaggedShape
 {
     private Shape shape;
     private String customId;
     // ... add some public getters and maybe setters for shape and customId here ...
     public TaggedShape(Shape s, String id)
     {
          shape=s;
          customId=id;
     }

     public Shape getShape(){return shape;}

     public void draw(){shape.draw();}
 }

Now, use the TaggedShape whereever you need shapes with an Id in your program, and a standard Shape whereever you don't require an ID. And whereever you need a TaggedShape as a Shape, use taggedShape.getShape().

As you see, this solution does not force you to implement the same ID code for each new derived class of Shape. It uses composition for the ID part, and inheritance for the draw part. So each new Shape derivation can be combined with the ID, or not, without any necessity to implement some ID interface again. And the Shape class has only "one reason to change" - when the drawing rules change, that's all. If ID part needs to be changed, the Shape class hierarchy is not directly affected any more.

As a final note: in this example, the draw method is purely abstract, so Shape could also be an interface. But all I wrote above stays the same even if there would be some implementation inheritance involved, as long as it belongs to the "drawing responsibility". For example, draw migh not be abstract, but provides an overridable default implementation in Shape. That is still a valid use case for inheritance.