Monday, August 13, 2012

I Can Haz Dependencz!

Lately we've been looking at the SOLID principles, taking a little bit of time to see what they are and how they can help us build flexible systems. This time we're going to look at The Dependency-Inversion Principle.

DIP states:

    "A. High-level modules should not depend on low-level modules. Both should depend on abstractions."
    "B. Abstractions should not depend on details. Details should depend on abstractions."

Um... what?

The core idea we are looking at here is that as much as possible, the system you are building should be dealing with abstractions instead of implementations. And that when a portion of your code needs something to perform its tasks, it asks for it instead of having the knowledge to get it itself.

Its polite to ask

So, why should our code ask for the things it needs? Its fairly straight forward really, by not asking for what it needs our code becomes coupled to its dependencies. Once you start coupling your code it loses its ability to be flexible or re-useable without going back in and doing serious changes. DIP is here to help you be able to change out any layer of your design for a different implementation of that layer.

Lets take a look at this:

    class Knight : ICanCarryStuff, ICanLootThings
    {
        init()
        {
            // create backpack
        }

        Equip(IEquipableItem item, IEquipLocation location)
        {
            // place existing item in location back into backpack
            // get item from backpack
            // equip item to location
        }

        Pickup(ICanBeCarried item)
        {
            // if enough room in backpack
                // put item in backpack
        }
        ...
    }

There's a lot more we could declare there, but this gives us a good idea of one of our heroes dependencies; the backpack. This being a simple game, we had just created the backpack when the hero was initialized and all was good. That is, until just about anything meaningful with the backpack changed. Now we have to change our hero code.

That sounds like we're breaking some other principles too. What happens when we implement the Merchant hero like we had previously talked about, would they have a backpack to start with or would hey have their cart? What if we gave away special promotion codes that all a new character got a bling-plated backpack when they upgraded their game to the deluxe edition.

Now what?

Ok, so we have the issue of our hero currently being tied to the backpack. Let's fix it:

    class Knight : ICanCarryStuff, ICanLootThings
    {
        init(IItemContainer container)
        {
            _container = container
        }

We've inverted the control. Now anywhere we want to create our hero, we are going to have to give us the pass in the dependency, no matter what implementation we want at the time.

What should we pass in?

Now lets look at another part of our code. We had some bad code sitting around in our last examples. Here we are already passing in a dependency, the 'Monster', to our attack code.

    interface ICanAttack
    {
        Attack(Monster target)
    }

    class Knight : ICanAttack, ICanDefend, ICanMove
    {
        Attack(Monster target)
        {
            // hack & slash on the monster!
        }
        ...
    }

So here we are conforming to part of the Dependency-Inversion Principle by passing into Attack the instance of what we want to actually attack. However we are also creating part of the problem that this principle helps us with; we aren't depending on abstractions. Just like the example we used in Liskov Substitution, the first time or any time afterwards, that we want to be able to attack something that isn't a monster we are going to break the Open-Closed principle and have to modify our system instead of extending it.

See what we did there, we just looked at a problem from the context of many of the principles. A quick side track; these principles work together to build a flexible system. The more you come to understand them and use them, the more they help each other with the issues they are tackling individually.

IGetAttacked

Back to the example at hand; here we can fix the attack method to ask for an IGetAttacked instead of a Monster, and then life goes back to being good. We can now extend our system by making anything we want to be attacked to implement the IGetAttacked. Now we can implement our heroes going bad and attacking our innocent shop keepers, or worse... other players.

    class Knight : ICanAttack, IGetAttacked, ICanDefend ...
    {
        Attack(IGetAttacked target)
        {
            // hack & slash whatever I want to!
        }
    }