The following article explains the usage of the composition design pattern in environments that do not allow multiple-inheritance of classes. Note: All code examples are written in C#.
In Java and .NET multiple-inheritance can be achieved through interfaces, but not through classes. In order to compensate for this, we can use the composition design pattern. In many cases, the composition pattern provides a better implementation than multiple-inheritance of classes because it is not concerned with merging the behaviours of multiple ancestor objects. The composition design pattern defines an object which holds instances to ancestor objects (as opposed to inheriting them). In order for the object to behave like its ancestors, it must also inherit interfaces for those objects.
Consider the following code, which demonstrates the composition design pattern:
//Interface to object A
public interface IA
{
void PrintMessage();
void DoA();
}
//Interface to object B
public interface IB
{
void PrintMessage();
void DoB();
}
//Concrete implementation of object A (implementing interface IA)
public class A : IA
{
public void PrintMessage()
{
Console.WriteLine("Hello from A");
}
public void DoA()
{
Console.WriteLine("Do Job A");
}
}
//Concrete implementation of object B (implementing interface IB)
public class B : IB
{
public void PrintMessage()
{
Console.WriteLine("Hello from B");
}
public void DoB()
{
Console.WriteLine("Do Job B");
}
}
//Concrete implementation of object C (implementing interfaces IA and IB)
public class C : IA, IB
{
private A a = new A();
private B b = new B();
public void PrintMessage()
{
a.PrintMessage();
b.PrintMessage();
}
public void DoA()
{
a.DoA();
}
public void DoB()
{
b.DoB();
}
}
Objects A and B can be assigned from IA & A and IB & B respectively. Object C can be assigned from IA, IB and C, but not A or B (as with multiple-inheritance). Regardless of design pattern or implementation, without multiple-inheritance, C can never be assigned from A or B, unless it directly inherits one of these classes, and even then it will only inherit that one, and not the other. Additionally, with regards to maintainability, it is not instantly visible that C is using the composition design pattern.
In some instances, generics can improve the way the composition design pattern is implemented. Suppose using the example above, class C only inherits IA and IB, but never inherits a concrete class. This leaves C open to inherit from one concrete class.
Consider the following code sample which implements the composition design pattern:
public abstract class CompositeModel<T1, T2>
{
//object instances are visible through (single) inheritance
protected T1 instanceOfType1;
protected T2 instanceOfType2;
//default constructor should not be called since T1 and T2 are unknown at this point.
private CompositeModel()
{
}
//this constructor allows implementations to be passed in at runtime.
public CompositeModel(T1 inst1, T2 inst2)
{
this.instanceOfType1 = inst1;
this.instanceOfType2 = inst2;
}
}
Given that the generic class performs the instantiation of ancestor classes, we can now define class C as follows:
public class C : CompositeModel<A, B>, IA, IB
{
public C() : base(new A(), new B())
{
}
public void PrintMessage()
{
this.instanceOfType1.PrintMessage();
this.instanceOfType2.PrintMessage();
}
public void DoA()
{
this.instanceOfType1.DoA();
}
public void DoB()
{
this.instanceOfType2.DoB();
}
}
By using the given generic class, the developer no longer needs to concern themselves with understanding or maintaining the composition of A and B in class C. With regards to maintainability, it is now much easier to recognise that C is using the composition design pattern because it inherits from CompositeModel, and we can also distinguish which object types are being used to create the composition given the generic type arguments. In addition to this, class C maintains implementation of IA and IB, and therefore should remain backwards compatible with existing code.
class C can be assigned in the following ways:
C instance = new C();
IA instance = new C();
IB instance = new C();
CompositeModel<A, B> instance = new C();
Assigning C like so: CompositeModel<A, B> instance = new C(); is actually pretty useless as CompositeModel does not implement any methods from its generic type parameters. CompositeModel is only concerned with the instantiation and coupling as part of the composition design pattern. The rest is maintained in any related interfaces and derived classes (i.e. C)
This implementation lends itself to both loosely and tightly coupled composition models since both classes and interfaces can be used as generic type parameters.
Consider the following code example which implements a tightly coupled composition model:
public class C : CompositeModel<A, B>, IA, IB
{
Public C() : base(new A(), new B())
{
}
public void PrintMessage()
{
this.instanceOfType1.PrintMessage();
this.instanceOfType2.PrintMessage();
}
public void DoA()
{
this.instanceOfType1.DoA();
}
public void DoB()
{
this.instanceOfType2.DoB();
}
}
This model is tightly coupled because the CompositeModel is always expecting A and B (or descendants of these concrete classes). Class C's constructor calls the base constructor and passes in instances of type A and B (or descendants).
Consider the following code example which implements a loosely coupled composition model:
public class C : CompositeModel<IA, IB>, IA, IB
{
Public C(IA a, IB b) : base(a, b)
{
}
public void PrintMessage()
{
this.instanceOfType1.PrintMessage();
this.instanceOfType2.PrintMessage();
}
public void DoA()
{
this.instanceOfType1.DoA();
}
public void DoB()
{
this.instanceOfType2.DoB();
}
}
This implementation allows anything assignable from IA and IB to be instantiated via the CompositeModel, therefore is not tied down to just A and B concrete implementations or descendants. C’s default constructor could be implemented to use A and B, however another constructor here provides access for the developer to pass in their own implementations of IA and IB.