Friday, August 10, 2012

You want that, to do what?

Last we looked at The Open-Closed Principle and learned how to build our code, open for extension while keeping them closed from modification. There we put together an interface for your hero:

    interface IHero
    {
        Attack(Monster target);
        Defend();
        Move();
    }

And then to build out our game from just having a knight, to having both a knight and a wizard:

    class Knight : IHero
    {
        // knightly stuff
    }

    class Wizard : IHero
    {
        // wizardly stuff
    }

So we have our new hero and through out the system we utilize what they can do like so:

    class Battle
    {
        Collection _heroes;
        Collection _monsters;

        Setup()
        {
            ShowHeroes();
            ShowMonsters();
        }
       
        PerformTurn()
        {
            _heroes.ForEach(hero => 
            {
                askForActionFrom(hero, possibleMonsterTargets);
            }
            
            _attackActions.ForEach((hero, monster) => 
            {
                hero.Attack(monster);
            }

            _defendActions.ForEach(hero =>
            {
                ...
            }
            ...
        }
    ...
    }

Something like that, should suffice to get us started with an actual battle, pitting our heroes of legend against the vile monsters from lore. Well we get that built out, release it to the public and they love it! Then the requests come in again, "Man, Knights are awesomer than Wizards for sure! But now I want to play a merchant!"

The Request

Simple enough, we already updated our system to handle unlimited hero types. The problem though, my doctor told me I had to take a break and couldn't look at code for a couple of days. Fearing the wrath of my community, I ask a buddy who's really into merchants and such to build out the system while I'm away. So what's he put in?

    class Merchant : IHero
    {
        Attack()
        {
            // throw exception("You couldn't pay me to lift a finger!")
        }
 
        Defend()
        {
            // throw coins at the problem
        }

        Move()
        {
            // horse & cart, need to carry my goods!
        }
    }

He then sends me an email detailing his knowledge of merchants in the fantasy dark ages era and their impact on the culture of the knight/merchant/pesant caste. Further stating how he got the story nailed down for me. So following my doctors orders of not looking at code, and my trust in him knowing what a merchant would do back then, I tell him to ship it.

And then...

My email box starts blowing up from my error notifier...

Turns out, everyone who is playing merchants gets a crappy game crash every time they try to attack something. I guess my buddy is the only one who thinks a merchant hero shouldn't be able to beat on monsters.

I call my buddy frantically, "Hey, I can't touch code, fix it!". He's like, "I got this." Still defending his position on, "Merchants couldn't be bothered to attack things", guess what we end up with...

    class Battle
    {
        ...
       
        PerformTurn()
        {
            ...
            _attackActions.ForEach((hero, monster) => 
            {
                if(hero != typeof(Merchant))
                    hero.Attack(monster);
            }

The code gets shipped and the error emails disappear. All is well in life.... Only now, we have to track every type of hero in these methods. As I was tired of the fans sending me emails begging for new heroes to play, the next release is to have 26 new hero types coming. How many of those wouldn't be doing 'Attack', or for that matter one of the other actions available. Our code is going to quickly get out of hand.

So, what now?

Ladies and Gentlemen; The Liskov Substitution Principle!

The principle states:

    "Subtypes must be substitutable for their base types."

The core idea here is that whether it be through inheritance, working with contracts or whatever, that your classes need to implement them in their entirety. By saying that we have a new hero type that can't do everything a hero can do, or does something outside of what it was intending, that it will lead to problems. After all, our system knows about IHero, and this Merchant isn't a complete hero unless he can actually attack.

What do we do?

Let's humor my buddy, and not force merchants to attack. Also, lets fix the issue at hand without creating crazy code in our battle class to handle the numerous hero types that can come in. We're going to change our idea of what a hero is, and break up the IHero interface into smaller behavior contracts. Just for sake of this post, we'll ignore the other parts of IHero, just working with the Attack action, we'll visit the others again in a different principle later.

    interface ICanAttack
    {
        Attack(Monster target);
    }

    class Knight : ICanAttack, ICanDefend, ICanMove
    { ... }

    class Wizard : ICanAttack, ICanDefend, ICanMove
    { ... }

    class Merchant : ICanDefend, ICanMove
    { ... }

See what we did there? The Merchant really can't attack now. So next up, we tackle the battle class to handle the new hero representation:

    class Battle
    {
        _allHeroes;

        _heroesWithAttacks;
        _heroesWithDefense;
        _heroesWithMoves;

        PerformTurn()
        {
            _allHeroes.ForEach(hero => 
            {
                askForActionFrom(hero, possibleMonsterTargets);
            }

            _attackActions.ForEach((hero, monster) => 
            {
                if(!_heroesWithAttacks.contains(hero))
                    continue; // skip this hero

                hero.Attack(monster);
            }

        ...        
    }

Ok, so the battle class still isn't all that great, but its no longer sucky from breaking the Liskov Substitution Principle. You can go back to creating any number of hero types and only include the types of actions you want the system to allow them to do. Next time we come back to these principles, we'll look at the one that gives us more insight on what we did with breaking the IHero up into ICanAttack, ICanDefend, ICanMove.