Coupling between classes can only be lowered by using interfaces or abstract classes. The idea is to swap out the implementation of one of these, and the other classes that depend upon those interfaces will continue to work as before. Therefore, if class A uses an interface B, and you have classes C and D as implementations of B, A is not coupled directly to C or D. If A contains C or a D as concrete implementations, than A is coupled to C or D.
Following the same logic. If A inherits B, then you can't swap B for another implementation, therefore there's a high coupling between A and B (unbreakable one once you shipped a framework for example).
Therefore...
Composition leads to lower coupling if and only if the composing members are interfaces or abstract classes. Otherwise it is the same coupling. However, even if you do have concrete classes inside a composition, you still have the advantage of being able to maybe extract an interface or abstract class out of these. So if your class uses only interfaces to do its job, then you can swap the implementation of any of those interfaces, and there is no tight coupling between your class and any of the implementations.You can't really do this with inheritance (some tools will consider inheritance as higher coupling because of this, but this is subjective). This is assuming SRP has be followed here. You can still create horribly coupled classes using composition. Simply pulling out some functionality out of your class in another class is not going to lower coupling.
There's 2 basic rules that you can follow to figure out if you should use inheritance or composition.
- If class A is a specialization of B, then A inherits B (i.e inheritance). It is like a dog is a specialization of carnivorous animal.
- If class A needs something that B can do, or has a B, then B should be a member of A (i.e composition). A dog needs bones to move, but you can't say a dog inherits bone, right?
Your example clearly demonstrates a high coupling with composition. The class DogComposition
shouldn't depend on the concrete AnimalComposition
. It should depend on the interface AnimalCompositionInterface
. To be 100% to the book, the instance of AnimalCompositionInterface
should be passed as a parameter in the constructor. That way, if a class AnimalComposition2
comes to be, you can swap AnimalComposition
and AnimalComposition2
without modifying DogComposition
. Also DogComposition
shouldn't implement AnimalCompositionInterface
. You basically implemented the facade pattern around AnimalCompositionInterface
.
//INHERITANCE TURNED INTO COMPOSITION
interface AnimalCompositionInterface {
public int makeSound();
}
class AnimalComposition implements AnimalCompositionInterface{
@Override
public int makeSound() {
return 0;
}
}
class DogComposition{
AnimalCompositionInterface animal;
public DogComposition(AnimalCompositionInterface impl) {animal = impl;}
@Override
public int makeSound() {
return animal.makeSound();
}
}
public static void main(String[] args) {
DogInheritance dogInheritance = new DogInheritance();
int dogInheritanceSound = dogInheritance.makeSound();
DogComposition dogComposition = new DogComposition(new AnimalComposition()); //you can swap implementations here
int dogCompositionSound = dogComposition.makeSound();
}
The inheritance hierarchy is clearly coupled because the derived class uses the abstract's class public method to do things. So DogInheritance
is coupled to AnimalInheritance
. You can't swap AnimalInheritance
for some other class with a different method to override.
EDIT #2
Since you updated the example to use the code above, the idea is basically that your composition is now coupled to the interface AnimalCompositionInterface
and not to the concrete implementation of AnimalComposition
. Since interfaces do not have actual implementation, this is considered acceptable coupling because you are essentially coupling to nothing. Or that you do not directly couple DogCompsotion
to AnimalComposition
, but use an intermediary instead. Interface coupling is needed to ensure type safety. Your example is a bit too simple to properly see the merit in using composition over inheritance. The problems discussed here occur in deep inheritance hierarchies with many public and protected methods.