DEPENDENCY INVERSION
If a class relies on another class, abstract that reliance.
The idea behind Dependancy Inversion is that whenever one class calls into another class, we should replace that call with some kind of abstraction to better insulate the classes from each other.
Most preferable is to replace the class dependance with an interface. So class A instead of calling methods in class B, actually calls methods of ISomeInterface which class B implements.
Another way is to introduce an abstract base class which class B inherits from. So class A treats class B as the base class instead of directly relying on class B itself.
The least preferable reliance, of course, is to have class A directly relying on class B. This is the one we want to avoid.
So in our previous example, those components may have a fairly tight coupling with each other. PlayerInput may rely on the presence of PlayerPhysics, WeaponManager may rely on the presence of Inventory, and so on.
Let’s abstract these.
IActorPhysics – Interface for classes which can accept input and presumably affect physics simulation
IDamageable – Interface for classes which can accept damage
IInventory – Interface for classes which can store and retrieve items
Now our PlayerPhysics implements IActorPhysics, our Health implements IDamageable, and our PlayerInventory implements IInventory.
So then PlayerInput just relies on the presence of some IActorPhysics, WeaponManager relies on the presence of some IInventory, and perhaps something which deals damage to our player relies on the presence of some IDamageable.
Dependancy Inversion helps reinforce Single Responsibility to some extent. PlayerInput shouldn’t care how the player moves, nor should it care what is going to move the player. Just that there’s something which promises that functionality.
Another thing to keep in mind is that SendMessage can accomplish the same goals in some cases.