Tuesday, August 14, 2012

Break it down!

As we have found out as we have been going over the principles, they tie into each other, solving each others issues. As we go over this one, you'll see that we've already started using it in our other examples and that it directly ties into the Single-Responsibility Principle. And now for the final principle from the SOLID principles: The Interface-Segregation Principle.

ISP states that:

    "Clients should not be forced to depend on methods that they do not use."

The 411

What ISP is telling us to watch for, is to keep our interfaces from becoming bloated, from taking on more than one responsibility, and to keep them cohesive. That any part of your system that utilizes the interface should need all that it offers, or you are offering to much. Interfaces that pass around methods that aren't needed, couple those areas of the code to the interface. When an area of the code causes the interface to change, where else would the change spread?

So what do we do to conform to ISP? We break up the interface into smaller cohesive interfaces.

    interface IShopKeeper
    {
        ShowStock();
        Buy(Item item);
        Sell(Item item);
        Talk();
        GiveQuest();
        CompleteQuest();
        Repair(Item item);
        CraftItem(Recipe recipe);
        ...
    }

Big interface, lots of reasons it could change, decent amount of places that could be coupled together unnecessarily. Lets segregate the interface to its logical cohesive areas.

    interface IVendor
    {
        ShowStock();
        Buy(Item item);
        Sell(Item item);
    }

    interface IStoryProvider
    {
        Talk();
        GiveQuest();
        CompleteQuest();
    }

    interface ICraftsman
    {
        Repair(Item item);
        CraftItem(Recipe recipe);
    }

    class ShopKeeper : IVendor, IStoryProvider, ICraftsman
    {
        ...
    }

Now the interface has been broken up, the individual parts can be added separately to different characters, and the different parts of the system only know and deal with the interfaces they should be dealing with.

SOLID

These issues are directly tied to the other issues we encountered when looking at the other SOLID principles. Let's look some of our previous code that put together in these examples:

Single-Responsibility

    interface ICharacter
    {
        Attack();
        Defend();
        EquipItem(Guid itemId, string location);
        GainExperience(int xpValue);
        ...

became:

    interface ICombatActions
    {
        Attack();
        Defend();
    }

    interface IEquipmentManagement
    {
        EquipItem(Guid itemId, string location);
    }

    interface ILevelCapable
    {
        GainExperience(int xpValue);
    }

Liskov Substitution

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

became:

    interface ICanAttack
    {
        Attack(Monster target);
    }

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

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

    class Merchant : ICanDefend, ICanMove
    { ... }