Object Oriented
In OOP; we often hear the phrase "it's best to favour composition over inheritance" -- so let's explore a little what that means and whether it's the only thing we think about when we reference composition.
Inheritance
In
object-oriented programming,
inheritance is the mechanism of basing an
object or
class upon another object (
prototype-based inheritance) or class (
class-based inheritance), retaining similar implementation.
For examPle:
C#:
// Inheritance (composition of classes)
public class Person { }
public class Employee : Person { } // Inherits the attributes of Person
public class Plumber : Employee { } // Inherits the attributes of both Employee and Person
Classes and objects created through inheritance are
tightly coupled because changing the parent or superclass in an inheritance relationship risks breaking your code.
Note:
Inheritance itself is a process of composition; where the child class is a composition of the attributes of its parent class and its own attributes.
Object Composition
In
computer science,
object composition is a way to combine
objects or
data types into more complex ones. Compositions relate to, but are not the same as, data structures, and common ones are the
tagged union,
set,
sequence, and various
graph structures, as well as the
object used in object-oriented programming.
"it's best to favour composition over inheritance"
C#:
// Composition (favor over inheritance)
// Enables dependency injection
class Contact { }
class Address { }
class Customer {
public Address[] Addresses { get; }
public Contact[] Contacts { get; }
}
Classes and objects created through composition are
loosely coupled, meaning that you can more easily change the component parts without breaking your code.
Interfaces
It is however not strictly true that composition on its own does enough to avoid tight coupling... because the property composition itself can be considered tight coupling and to avoid that the properties can be composed using defined interfaces.
Meaning that any class conforming to the interface could be interchangeable as the instance for that property -- which is the underlying concept of
dependency injection.
Aside from helping to avoid tight coupling with the composition of property types in a class; interfaces themselves can also be compose.
C#:
// Composition of interfaces
/// Composition of interfaces
public interface IPlumber { }
public interface IElectrician { }
public interface IHandyman : IPlumber, IElectrician { }
The
IHandman is a composition of the conformance requirements for both the
IPlumber and the
IElectrican.
This flexibility is however still not a perfect solution because you end up with an interface whose definition is tightly coupled to the classes that conform to it; meaning you can't change the interface without affecting all the classes that are dependent on it.
The only way to work around this problem prior to C# 8.0 was to create a new interface with the added conformances and then add that as an new conformance requirement to the classes where you needed this.
Default Interface Implementations
With C# 8.0 and default method implementations for interfaces this limitation is upgraded; because you can now add new functionality to existing classes that conform to the interface without the requirement to change all these classes. This is done by adding a default implementation of the methods to the interface, which exposes this new functionality to all dependant objects without the need for code changes. The need for this was derived primarily by C# Xamarin framework; specifically its need to operate in both the Android Java world and the iOS Swift world -- because both of those languages have had support for default interface method implementations.
Side Note:
In Swift, interfaces are called protocols, and a style of programming using default method implementations for interfaces is called
Protocol Oriented Programming (POP); a technique that also leans heavily on interface and class extensions; similar to how
Linq is implemented.
The equivalent style of programming in C# would probably have to be called Interface Oriented Programming. Java doesn't (yet?) have support for extension methods so it's not (yet?) possible to duplicate this style of code design.
This is an new area of design to be explored in C# since 8.0; to further reduce the tight coupling in OOP design; the combination of both extensions methods and default interface implementations make it possible to extend the functionality of existing classes without the need to modify to adopt the interface conformance changes. It not quite as flexible as Swift (yet?) re extension methods cannot be used interchangeably with default interface methods; and overall whilst this adds flexibility to C#; it still has its limits and most of these are fairly widely discussed in Swift blogs posts about POP.
Inheritance and Interface Composition
We can also mix the composition of both Inheritance and composition using classes and interfaces, for example:
C#:
// Inheritance (composition??) of classes & interfaces
// Inheritance (composition??) of classes & interfaces
class EmployeeA1 : Employee, IPlumber {
public Address[] Addresses { get; }
public Contact[] Contacts { get; }
}
class EmployeeA2 : Employee, IElectrician {
public Address[] Addresses { get; }
public Contact[] Contacts { get; }
}
class EmployeeA3 : Employee, IHandyman {
public Address[] Addresses { get; }
public Contact[] Contacts { get; }
}
...and of course all the inheritance could be swapped out for composition and the composition of Classes could be swapped out for Interfaces, etc.
Composition of values
All OOP languages include as part of their standard library the ability to compose values together, for example:
C#:
// Integer composition using the binary + operator
1 + 2 + 3
// String composition using the binary + operator
"the" + "movement" + "of"
Composition of logical conditions
Similarly the ability to create a composition of logical conditions, for example:
C#:
if (person.Name.startWith("A") && person.Sex == Sex.Male) || (person.Name.startWith("B") && person.Sex == Sex.Female) { }
for (var i = 0; i < 10 && j < 20 ; i++) { }
while (inputChar.isWhiteSpace() && !stream.EOF()) { }
Composition of function / method calls
We can also build up a new process as a composition of function / methods calls, for example:
C#:
// Composition of function calls
public static class Circle {
public static double Area(double radius) {
return Math.PI * Math.Pow(radius, 2d);
}
}
Math.PI is a function that returns the mathematical value for
PI and
Math.Pow is a function to raise a number to a specified power i.e. squared.
Naturally these composition of functions calls and method calls can be substantially more complexly interwoven -- the ultimate benefit of this style of composition is to simplify the work of a single function / method in order to simplify testing. This style programming is collectively termed the
Single Responsibility Principle (SRP).
OOP Summary
That roughly covers what can be thought of as composition from the Object Oriented Programmer's point of view.
Next we'll move onto the functional view of what constitutes composition; many concepts of which have become seamlessly integrated into most modern OOP languages.