ghytred.com


NewsLook for Outlook

Patterns and OO
StrategyPattern
The Web Site you seek
Cannot be located, so
Waste your time here

Contents

Strategy

From GoF Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithms vary independently from clients that use it. A Decorator lets you change the skin of an object; a Strategy lets you change the guts.

If an object has a responsibility which can be fulfilled in many different ways, the appropriate one being chosen at run-time, Strategy is one of the ways of structuring the code; Decorator from last week is another. The difference between them is partly in the usage. Sometimes Decorator is not easy to use; for example, the Circle from the Decorator article. In actual use in a WinForm or a WebForm, the base Circle would be a member of the Form's Control collection. To get the Decorated Circle drawn, the Circle would have to be replaced in the owning control's .Controls collection by the final Decorator. This is kludgy. It's far better to implement the behaviour via Strategy.

Using Strategy, the Circle would be given a Strategy object which contained the appropriate drawing instructions. When the Circle is asked to Draw, in its Draw method, it would use that Strategy object to do the work, passing it data about its centre and radius.

It might look like this:

public class Circle {
	public virtual void DrawStrategy (DrawStrategy strategy) {
		this.drawStrategy = strategy;
	}
	public virtual void Draw(Graphics g, Pen p){
		this.drawStrategy.Draw(this.Center, this.Radius);
	}
}

Who gives the Circle the appropriate Strategy? The same person who decorated the circle; it is unlikely to be the person who asks the circle to Draw � but might be, sometimes.

It is not immediately possible to 'strategise' the subject (Circle) in the way that a Decorator can surround a subject, however the Strategy itself can be decorated to get the same effect.

Unlike Decorator, the object using the Strategy must be created to use a Strategy; one can't be bolted on later. However, any number of new Strategies can be added once an object is set up to use one.

The using object is coupled to the Strategy, but not the other way round; thus a Strategy can potentially be used by many different objects. Also unlike a Decorator, an object can decide for itself to use a Strategy, generating the correct one internally; an object cannot choose to be decorated or not or by what.

One feature of Strategy is that it needs to be passed context from its user � the centre and radius in the example above. This context data is usually not required except while the strategy is actually being executed: it does not have to hold the context as state. This means that the same Strategy instance can be re-used by many differing clients. This fits it very well for the Flyweight pattern. Again, this is very different to Decorator where the Decorator holds the instance it is decorating for its useful life.

Decorator and Strategy can be seen as two ways to achieve the same end, applicable in differing circumstances. However, Strategy has other areas of applicability also. It can be used to rationalise code where behaviour is conditional on various factors; rather than have complicated if/switch constructs containing different ways of doing a task, the if/switch construct can be used to decide on an individual strategy object to use, and then use it.See Yonat Sharon's OO-Tips site for a nice justification of Strategy instead of switch/if (that whole site is worth reading!).

It may also be possible to break out the decision-making code for the strategy to use into a Factory class, simplifying the strategy-user even more. Of course, this is only useful if the strategy is used in more than one place and the decision rules are the same.

Strategies can be nested. Part of the algorithm that the initial Strategy uses can be varied via a secondary strategy depending on some of the context data. This can be repeated indefinitely.

One 'problem' with Strategies is that not all concrete strategies use all the context passed to them. To keep the interface the same, the greatest possible context must be passed, but this will be wasted on simple strategies. One solution is to pass the using object itself; this allows the strategy to ask for the data it needs directly, but has the disadvantage of coupling the strategy to one using class.

A solution to this is to have the users implement an interface that the Strategy expects and can query to get the data required to implement its algorithm. This is a nice solution, but should be used wisely; it complicates the code structure and should be used with care.

public class StrategyUser : IMyStrategyForXyzzyInfo {
	public void DoSomething() {
		IMyStrategyForXyzzyInfo isi = this as IMyStrategyForXyzzyInfo
		this.myStrategy.DoSomething(isi);
	}
}

Mediator is a 'heavy' strategy, where more than one class must provide the context for the calculation. Mediators can end up using multiple strategies.

Consequences from GoF:

  • Families of related algorithms
    • Algorithms are arranged into an inheritance hierarchy.
    • Similarities between algorithms can be moved into the Strategy base class.
  • An alternative to subclassing
    • Achieves a clearer, more extensible design than subclassing context.
    • The algorithm can be varied independent of the context
    • The algorithm can be changed dynamically at run time.
  • Strategies eliminate conditional statements
    • When the strategies are lumped into one class it is hard to avoid conditionals
    • Conditional statements are hard to maintain, bulky and create unnecessary dependencies
  • A choice of implementations
    • Clients can chose amongst different implementations with different time and space trade-offs
  • Clients must be aware of different strategies
    • One drawback is that clients must be aware of the effects of different strategies
    • Exposes clients to implementation issues
  • Communication overhead between Strategy and Context
    • Be wary of passing a lot of data between Context and Strategy in which most of the concrete Strategy classes do not use this data.
    • Use tighter coupling or an Interface to avoid this situation
  • Increased number of objects
    • This pattern increases the number of objects in an application.
    • This overhead can be reduced by implementing strategies as stateless objects and maintaining state in the context. The concrete strategy instances can then use the Flyweight pattern to share instances across multiple users.

A Flyweight implementation of a Strategy:

    static void Main(string[] args) {
        for (int i = 0; i < 26; i++) {
            string type = "ABC".Substring(i % 3, 1);
            Strategy strategy = StrategyFactory.Strategy(type);
            Context c = new Context(strategy);
            c.DoSomething();
        }
    }
    public class Context {
        public Context(Strategy concreteStrategy) {
            this.concreteStrategy = concreteStrategy;
        }
        private int someState;
        private Strategy concreteStrategy;
        public int SomeState {
            get { return this.someState; }
            set { this.someState = value; }
        }
        public void DoSomething() {
            this.concreteStrategy.DoWhatever(this.someState);
        }
    }
    public abstract class Strategy {
        protected int instrinsicState;
        public virtual void DoWhatever(int helpfulData) {}
    }
    public class ConcreteStrategyA : Strategy {
        internal ConcreteStrategyA() {
            instrinsicState = 12;
        }
        public override void DoWhatever(int helpfulData) {
            // do whatever in a particular way
        }
    }
    public class StrategyFactory {
        private static Hashtable strategies = new Hashtable();
        public static Strategy Strategy(string strategyType) {
            Strategy strategyToReturn = null;
            if (!strategies.Contains(strategyType)) {
                switch (strategyType) {
                case "A" :
                    strategyToReturn = new ConcreteStrategyA();
                    break;
                // other cases
                }
                strategies.Add(strategyType, strategyToReturn);
            } else {
                strategyToReturn = 
			strategies[strategyType] as Strategy;
            }
            return strategyToReturn;
        }
    }

Return to top