Introduction
Composition vs Inheritance is a wide subject. There is no real answer for what is better as I think it all depends on the design of the system.
There are many websites that also attempt to explain this argument, however many of them seem to try and “conclude” what the web author “personally” thinks is better from their experience. This article will try and compare composition and inheritance with the aim to provide their uses so you can make a logical choice of what to apply and when.
Definitions
1. Composition
A “Wiki” style definition: “object composition (not to be confused with function composition) is a way to combine simple objects or data types into more complex ones.”
My definition: Allowing a class to contain object instances in other classes so they can be used to perform actions related to the class (an “has a” relationship) i.e., a person has the ability to walk.
2. Inheritance
Another “Wiki” style definition: inheritance is a way to form new classes (instances of which are called objects) using classes that have already been defined.
My definition: One class can use features from another class to extend its functionality (an “Is a” relationship) i.e., a Car is a Automobile.
Class Diagram Examples
Software Example of Inheritance
This example will have two “fighter’s” that can attack another “fighter” with a move.
Diagram
Solution in C#
abstract class Fighter
{
public int Health = 100;
public int Damage;
public string AttackName;
public string Name;
protected Fighter(int damage,string attackName,string name)
{
Damage = damage;
AttackName = attackName;
Name = name;
}
public void Attack(Fighter fighter)
{
fighter.Health -= Damage;
}
}
class WeakFighter:Fighter
{
public WeakFighter() : base(5, "Weak punch","Weak fighter")
{
}
}
class StrongFighter:Fighter
{
public StrongFighter() : base(30, "Strong punch","Strong fighter")
{
}
}
static void Main()
{
Fighter fighter1 = new WeakFighter();
Fighter fighter2 = new StrongFighter();
Attack(fighter1, fighter2);
Attack(fighter2, fighter1);
Console.Read();
}
static void Attack(Fighter attacker, Fighter defendant)
{
Console.WriteLine("{0} Attacks with {1} with {2} damage ",
attacker.Name, attacker.AttackName, attacker.Damage);
attacker.Attack(defendant);
Console.WriteLine("Defendant {0} now has {1} health",
defendant.Name, defendant.Health);
}
}
This solution may not be designed very well but it's just an example of how inheritance could be used.
There are some problems with this code though:
- If you think about it, your program may have many fighters with many moves. If you follow this design, you may find that you have hundreds of sub classes (classes that are derived/inherited from the
Fighter
class. This could turn into a nightmare for managing code as giving each fighter a move and name is annoying, but if something went wrong it could take a while looking for the correct class to change. This is an “Inherent” problem with inheritance, abuse. - Let's say the moves can change name and damage, have fun trying to change all your sub classes!
- What if a move were to do multiple things, such as punch and kick and shoot a fireball? You could make a class (probably a hacked mash up if you follow strictly to this design) and also that screws with “Code duplication”.
When scenarios like this arise, I prefer to use composition as that can provide me with some overhead to extend and add new functionality without being dependant on changing “specialised” classes.
When inheritance is used, the .NET CLR will “Dynamically Bind” the methods (behaviour) associated with the base class to your derived class which is why inheritance is a form of “polymorphism” (many ways of doing something related). This means that if the base class (i.e. Fighter
) changes its behaviour or structure, all of its derived classes must also and as the example above, use an “Abstract Class”, if the behaviour in the “Attack
” method inside the “Fighter
” class changes, it will be automatically applied to class’s “WeakFighter
” and “StrongFighter
” – which is a benefit.
The downside of this is that derived classed depend on the base class or super class (if your Java) to do the work for them, adding a new method that some derived classes may not want to do means that you will have to override that specific method in the specialised derived class which is why management problems arise quickly with mass sub classing.
Change in the base class can cause a “ripple effect” in the code so for example if I wanted to change the return type of the “Fighter
” method “Attack
” to an “int
”, it will automatically break the code.
Software Example of Composition
This example is very similar to the previous example of “fighter”, however it makes the fighter more dependant on its behaviour that is encapsulated from the Move
class.
Diagram
Solution in C#
class Fighter
{
public int Health = 100;
public string Name;
private Move _move;
public string MoveName
{
get
{
return _move.Name;
}
}
public int MoveDamage
{
get
{
return _move.Damage;
}
}
public Fighter(string name, Move defaultMove)
{
Name = name;
_move = defaultMove;
}
public void Attack(Fighter defendant)
{
if (_move != null)
_move.Attack(defendant);
}
public void SetMove(Move move)
{
if (move != null)
_move = move;
}
}
abstract class Move
{
public int Damage { get; set; }
public string Name { get; set; }
protected Move(int damage,string name)
{
Damage = damage;
Name = name;
}
public void Attack(Fighter defendant)
{
defendant.Health -= Damage;
}
}
class PunchMove:Move
{
public PunchMove() : base(5, "Punch")
{
}
}
class KickMove:Move
{
public KickMove() : base(7, "Kick")
{
}
}
static void Main()
{
Fighter fighter1 = new Fighter("Puncher", new PunchMove());
Fighter fighter2 = new Fighter("Kicker", new KickMove());
Attack(fighter1,fighter2);
Attack(fighter2,fighter1);
Console.Read();
}
static void Attack(Fighter attacker,Fighter defendant)
{
Console.WriteLine("Fighter {0} Attacks {1} with {2} Move resulting in {3} Damage"
,attacker.Name,defendant.Name,attacker.MoveName,attacker.MoveDamage);
attacker.Attack(defendant);
Console.WriteLine("Defendant {0} has {1} health remaining",
defendant.Name,defendant.Health);
}
}
This solution is better as it allows the “
Fighter
” class to depend on “
Move
” to attack its opponent which is better as Moves can now be easily changed at run time instead of declared explicitly before hand.
It also makes more sense that a “Fighter
” has a “Move
”, don’t you think? The downside to this is that sub classes may need to be added for the Move
to be extended, so if hundreds of moves are inside your program, you run into a similar problem than what we reached before. That’s when you compose (use composition) on them as a way of normalising the data.
On a side note, the above “composition” code example is a simple idea of the “Strategy” design pattern that is used to encapsulate behaviour.
The good thing about composition is that as its reference in this situation is stored inside the class “Fighter
”, changing it may not break the code and if it does would be easy to fix for example if the method “Attack
” inside the “Move
” abstract
class was to do something else such as:
public void Attack(Fighter defendant)
{
defendant.Health -= Damage;
Random random = new Random();
defendant.Health -= random.Next(0, 4);
}
The code will not break anything in the “Fighter
” or “Program
” class.
Another thing to consider is now the “Fighter
” class only depends on “Move
”, it does not have its own values to store the attack name and damage and could reduce data duplication and its “Attack
” method just calls the “Attack
” method inside its composed “_move
” instance. This is good as fighter now “Has a move” which makes it easier to change.
Now that composition is applied to this class, its “ripple effect” only applies to the “Fighter
” class.
Conclusion
It's not too hard to decide what should be used over each other, inheritance is an “Is a” relationship and composition is an “Has a” relationship, these are powerful assets in programming software so think about how they can benefit each other when you use them.
Another thing to consider when using inheritance is its “Singleness”. When deriving, it can only inherit from one super class and because of this, complex structures could use composition to make them easier to manage yet composition normally uses inheritance from its own super class, other alternatives such as aggregation could help.
CodeProject