Intro:

This article has been inspired by a recent show on Channel 9, which demonstrates design patterns. While these have been around for a long time and certainly have had great influence on how I write code,  the implementation can actually get more lightweight in modern programming languages such as C# and recent Java. The main reasons for that are:

1. Design pattern is baked into the language. This is the case with Iterator (implemented with IEnumerator and foreach) and Observer (Events).

2. The ability to reference methods in forms of delegates gives more flexibility than having interfaces + classes that hold implementation.

I will not talk about the first category because people use these features instinctively at this point. However, the second category brings an interesting twist on existing design patterns.

 

Strategy

Allows the change of behavior of a method at runtime.

The problem this pattern solves is having to change the way something is done at runtime. This most commonly refers to some algorithm being swapped out dynamically, chosen at runtime from a family of algorithms, all having the same interface.

The key to the pattern is to have an object that calls a certain behavior through an interface. The specific implementation of that interface will be a class that only holds that algorithm.

Drawbacks:

  • Introduces classes that represent behavior, which are not an ideal conceptual fit. Behavior related to a class is inside another class.
  • Possible number of classes explosion.
  • Calls to the behavior must come through an interface, whether that is desirable or not.
  • Possible reckless creation of objects when switching a strategy.

Example:

class Program {
    static void Main(string[] args) {
        var myScrewdriver = new ParticularScrewdriverModel(new PhillipsHead());
        TestScrewdriver(myScrewdriver);
        myScrewdriver.ChangeHead(new SlottedHead());
        TestScrewdriver(myScrewdriver);
        myScrewdriver.ChangeHead(new HexHead());
        TestScrewdriver(myScrewdriver);

        Console.ReadKey();
    }

    static void TestScrewdriver(IModularScrewdriver screwdriver) {
        Console.WriteLine(screwdriver.Turn());
    }
}

public interface IScrewdriverHead {
    string Turn();
}

public struct PhillipsHead : IScrewdriverHead {
    public string Turn() => "Turning PhillipsHead";
}

public struct SlottedHead : IScrewdriverHead {
    public string Turn() => "Turning Slotted";
}

public struct HexHead : IScrewdriverHead {
    public string Turn() => "Turning Hex";
}

public interface IModularScrewdriver {
    string Turn();

    void ChangeHead(IScrewdriverHead iScrewdriver);
}

public class ParticularScrewdriverModel : IModularScrewdriver {
    private IScrewdriverHead _head;

    public string Turn() => _head.Turn();

    public ParticularScrewdriverModel(IScrewdriverHead head) {
        ChangeHead(head);
    }

    public void ChangeHead(IScrewdriverHead head) {
        _head = head;
    }
}

Explanation: this is the simplest explanation of Strategy I could come up with. Simply, we are changing a head on a “modular screwdriver” to represent the switch of algorithms. The problem from the last point is apparent here: what happens with the old instances of strategy objects whenever we change a strategy? They will simply be left up to the GC to collect when the time comes. This means that in this version we are generating a small amount of garbage every time we switch a strategy. This can be solved by using Structs on strategies themselves and while that will work, most people don't do it that way. In that case, the strategy instance will simply be embedded inside the class instance (the strategy owner, here ParticularScrewdriverModel). Another solution could be to make the strategies themselves state-less Singletons and rout all data through the method. I don't really like this approach because it decreases locality and creates more complexity in this already too complex code for what it's doing.

More modern example:

class Program {
    static void Main(string[] args) {
        var myScrewdriver = new ParticularScrewdriverModel(ScrewdriverHeadType.PhillipsHead);
        TestScrewdriver(myScrewdriver);
        myScrewdriver.ChangeHead(ScrewdriverHeadType.SlottedHead);
        TestScrewdriver(myScrewdriver);
        myScrewdriver.ChangeHead(ScrewdriverHeadType.HexHead);
        TestScrewdriver(myScrewdriver);

        Console.ReadKey();
    }

    static void TestScrewdriver(IModularScrewdriver screwdriver) {
        Console.WriteLine(screwdriver.Turn());
    }
}

public interface IModularScrewdriver {
    string Turn();

    void ChangeHead(ScrewdriverHeadType headType);
}

public enum ScrewdriverHeadType {
    PhillipsHead, SlottedHead, HexHead
}

public class ParticularScrewdriverModel : IModularScrewdriver {
    private Func _head;

    public string Turn() => _head?.Invoke();

    public ParticularScrewdriverModel(ScrewdriverHeadType headType) {
        ChangeHead(headType);
    }

    public void ChangeHead(ScrewdriverHeadType headType) {
        switch(headType) {
            case ScrewdriverHeadType.PhillipsHead:
                _head = PhillipsHead;
                break;
            case ScrewdriverHeadType.SlottedHead:
                _head = SlottedHead;
                break;
            case ScrewdriverHeadType.HexHead:
                _head = HexHead;
                break;
            default: throw new ArgumentException("Unexpected Head type: " + headType);
        }
    }

    string PhillipsHead() => "Turning PhillipsHead";
    string SlottedHead() => "Turning Slotted";
    string HexHead() => "Turning Hex";
}

There are a couple of design choices made here. First, we could have easily injected the strategy itself in the c-tor of the class and the ChangeHead() method. There are a coupe of problems I have with this approach: It holds the implementation away from the class that needs it. Thus, whoever actually has the implementing method knows too much about the class that's actually doing the operation. The particular details of the strategy should be inside the class that's doing the strategy or inside the separate class like before, not inside the class that does the actual switching (in our case static Main()). This variation would then look more like Template Method Pattern.

Also, this whole design pattern simplification could have been done even without delegates. If we simply held a private field with the enum and did the switch when calling the Turn() method. Not to mention this allows us to easily check which strategy (or "mode") we're in right now.


If you're finding this article helpful, consider our asset Dialogical on the Unity Asset store for your game dialogues.

 

Template Method

This pattern has certainly received much criticism because of it's ability to create class explosion. Let's say we have a situation where we want to sum numbers in an array only when they obey a certain rule. For example, right now we want to sum numbers that are odd or divisible by 5, but we may want to extend the behavior in the future. Also, combinations could be allowed in the future. We could use a Template method like so:

public abstract class SelectiveSummer {
    public int Process(List<int> inputs) {
        int result = 0;
        foreach(var input in inputs) {
            if(Filter(input)) result += input;
        }
        return result;
    }

    protected abstract bool Filter(int element);
}

public class EvenSummer : SelectiveSummer {
    protected override bool Filter(int element) {
        return (element % 2 == 0);
    }
}

public class DivisibleBy5Summer : SelectiveSummer {
    protected override bool Filter(int element) {
        return (element % 5 == 0);
    }
}

class Program {
    static void Main(string[] args) {
        List<int> inputs = new List<int>() { 1,2,3,4,5 };
        Console.WriteLine((new EvenSummer()).Process(inputs));
        Console.WriteLine((new DivisibleBy5Summer()).Process(inputs));

        Console.ReadKey();
    }
}

It's clear this approach doesn't work for this problem. Every time we want to create a new type of Summing operation we need to write a new class. Not to mention that for every use of yet unused summing operation we create a new class instance. Instead, we want to pass a method that describes the filtering. This method could either be anonymous or passed as a delegate from the caller site.

public class SelectiveSummer {
    public int Process(List<int> inputs, Func<int, bool> Filter) {
        int result = 0;
        foreach(var input in inputs) {
            if(Filter != null && Filter.Invoke(input)) result += input;
        }
        return result;
    }
}

class Program {
    static void Main(string[] args) {
        List<int> inputs = new List<int>() { 1,2,3,4,5 };
        Console.WriteLine((new SelectiveSummer()).Process(inputs, x => x % 2 == 0));
        Console.WriteLine((new SelectiveSummer()).Process(inputs, x => x % 5 == 0));

        Console.ReadKey();
    }
}

I would not say the Template method is not useful. There are cases where the encapsulation of the implementation works best like originally intended. Sometimes the caller does not know or should not know of the special methods involved in the operation. Sometimes the inheriting class would do many methods differently, and not all combinations of methods are valid. Overall, there is a particular class of problems where this variation is more desirable. As a side note, this example could have been a one-liner in LINQ, but that is not the point. I could have used the Transaction Wrapper type of method to demonstrate the principle and LINQ would not help there. 


If you're finding this article helpful, consider our asset Dialogical on the Unity Asset store for your game dialogues.


Log In:




Comments (0)