This article illustrates the concepts of inheritance vs. composition in Java. It first shows an example of inheritance, and then shows how to improve the inheritance design by using composition. How to choose between them is summarized at the end.
1. Inheritance
Let’s suppose we have an Insect
class. This class contains two methods: 1) move()
and 2) attack()
.
class Insect { private int size; private String color; public Insect(int size, String color) { this.size = size; this.color = color; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public void move() { System.out.println("Move"); } public void attack() { move(); //assuming an insect needs to move before attacking System.out.println("Attack"); } } |
Now you want to define a Bee
class, which is a type of Insect
, but have different implementations of attack()
and move()
. This can be done by using an inheritance design like the following:
class Bee extends Insect { public Bee(int size, String color) { super(size, color); } public void move() { System.out.println("Fly"); } public void attack() { move(); super.attack(); } } |
public class InheritanceVSComposition { public static void main(String[] args) { Insect i = new Bee(1, "red"); i.attack(); } } |
The class hierarchy diagram is as simple as:
Output:
Fly Fly Attack
“Fly” was printed twice, which indicates move()
is called twice. But it should be called only ONCE.
The problem is caused by the super.attack()
method. The attack()
method of Insect
invokes move()
method. When the subclass calls super.attack()
, it also invokes the overridden move()
method.
To fix the problem, we can:
- eliminate the subclass’s
attack()
method. This will make the subclass depends on the superclass’s implementation ofattack()
. If theattack()
method in the superclass is changed later (which is out of your control), e.g., the superclass’sattack()
method use another method to move, the subclass will need to be changed too. This is bad encapsulation. - rewrite the
attack()
method like the following:public void attack() { move(); System.out.println("Attack"); }
This would guarantee the correct result, because the subclass is not dependent on the superclass any more. However, the code is the duplicate of the superclass. (Image
attack()
method does complex things other than just printing a string) This does not following software engineering rule of reusing.
This inheritance design is bad, because the subclass depends on the implementation details of its superclass. If the superclass changes, the subclass may break.
2. Composition
Instead of inheritance, composition can be used in this case. Let’s first take a look at the composition solution.
The attack function is abstracted as an interface.
interface Attack { public void move(); public void attack(); } |
Different kinds of attack can be defined by implementing the Attack
interface.
class AttackImpl implements Attack { private String move; private String attack; public AttackImpl(String move, String attack) { this.move = move; this.attack = attack; } @Override public void move() { System.out.println(move); } @Override public void attack() { move(); System.out.println(attack); } } |
Since the attack function is extracted, Insect
does not do anything related with attack any longer.
class Insect { private int size; private String color; public Insect(int size, String color) { this.size = size; this.color = color; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } } |
Bee is a type of Insect
, it can attack.
// This wrapper class wrap an Attack object class Bee extends Insect implements Attack { private Attack attack; public Bee(int size, String color, Attack attack) { super(size, color); this.attack = attack; } public void move() { attack.move(); } public void attack() { attack.attack(); } } |
Class Diagram:
public class InheritanceVSComposition2 { public static void main(String[] args) { Bee a = new Bee(1, "black", new AttackImpl("fly", "move")); a.attack(); // if you need another implementation of move() // there is no need to change Insect, we can quickly use new method to attack Bee b = new Bee(1, "black", new AttackImpl("fly", "sting")); b.attack(); } } |
fly move fly sting
3. When to Use Which?
The following two items can guide the selection between inheritance and composition:
- If there is an IS-A relation, and a class wants to expose all the interface to another class, inheritance is likely to be preferred.
- If there is a HAS-A relationship, composition is preferred.
In summary, Inheritance and composition both have their uses, and it pays to understand their relative merits.
References:
1. Bloch, Joshua. Effective java. Pearson Education India, 2008.
2. http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
3. http://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition–which-one-should-you-choose-.html
Good information also may have a look at updated java tutorials are available at http://www.androidcoding.in/tutorials-java/
In that case, how about we further subclass Bee to get, for instance, WorkerBee and QueenBee, and only have the former implement an Attacker interface?
How about AttackBehavior?
Some bees HAVE AttackBehavior ?
why Insect i and why not Bee b? pardon my ignorance but I am just starting off
But some bees are attackers and some are not. Attacker seems to be a role that a bee HAS. How about that?
While I agree with your conclusions, I think the ‘is-a’ vs ‘has-a’ relationship isn’t really so clear-cut. For example, if you have a few insects defined that are implement Attack, you might have a collection of them, which might be something like:
Collection attackers = …
In this case, the Attack interface seems to be named incorrectly – we don’t have a collection of attacks. We have a collection of attackers. So, we could refactor the Attack interface to be Attacker.
Collection attackers = …
Now, a bee IS AN insect, but a bee also IS AN attacker, it doesn’t HAVE-A attacker. So just by renaming the interface to an arguably more correct name, the waters are muddied completely.
that should invoke the move() method of insect class?????
The answer to your question is the syntax to call method(Instance method) is obj.method() and the obj here refers to Bee Object. for calling methods specifically to Parent Class we use super keyword.
You can also write a small code and test it your self.
I have a doubt : in the first case , why should it print ‘fly fly attack’ and not ‘fly move attack’ because we called super.attack() , that should invoke the move() method of insect class ??
Good One.Thanks mate.