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:
-
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; }
-
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 aShape
with a polymorphicdraw
method, then using inheritance is a perfectly valid option for this part of the class model. If in your specific context each kind ofShape
object always, always, always needs anId
, with no exception, then putting the related implementation into theShape
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 theShape
has two different responsibilities: providing a genericdraw
interface and providing identifyability through an Id.Moreover, the
setCustomId
makes aShape
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 aTaggedShape
, which is composed of an Id and a shape:Now, use the
TaggedShape
whereever you need shapes with an Id in your program, and a standardShape
whereever you don't require an ID. And whereever you need aTaggedShape
as aShape
, usetaggedShape.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 thedraw
part. So each new Shape derivation can be combined with the ID, or not, without any necessity to implement some ID interface again. And theShape
class has only "one reason to change" - when the drawing rules change, that's all. If ID part needs to be changed, theShape
class hierarchy is not directly affected any more.As a final note: in this example, the
draw
method is purely abstract, soShape
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 inShape
. That is still a valid use case for inheritance.