Monday, January 15, 2007

Strategy Pattern vs Visitor Pattern

I have been trying to memorise Gang of Four design patterns recently, and came across the Strategy and Visitor patterns.

I struggled for a while to understand the difference between these two patterns, and the circumstances that would require one over the other.

I believe I understand correctly now, and thought it prudent to write it down somewhere. Here is as good a place as any for me to refer back to later, and it provides the added bonus of allowing people to correct me.

This post may not give you a good introduction into the Visitor and Strategy patterns, but will hopefully clarify when you should use one over the other (something I had difficulty finding a nice example of on the web).

Firstly allow me to provide an overview in my own words for each of the patterns:

Visitor Pattern
Consists of "Visitees" or "Hosts" and "Visitors". Hosts are objects within an object tree, and Visitors contain operations to be performed on these Hosts.

Hosts expose an Accept() method, which takes a Visitor object, and Visitors expose a Visit() method which has an overload for each Host. When the Accept() method is called on the Host, and a Visitor passed, a Visit() method is called on the visitor.

Using this pattern, operations become Double Dispatch, meaning they are executed based on two classes; the Host and the Visitor.

Strategy Pattern
Consists of a "Context" and a "Strategy". Contexts are objects within a tree related classes, and a Strategy is a class containing a series of operations to be used by the Contexts.

Strategy provides an interface of which Context objects are aware. When a Context object is created, a Strategy is also created (if not static) and given to the Context. Operations can then be selected from the selected Strategy as desired.

The Difference
While both patterns improve separation of concerns, and are fairly similar at face value, there are some important differences:

The Strategy pattern is designed to have the Strategy operation decided at runtime. Concrete Strategy objects can be passed to each Context operation. According to the Gang of Four Design Patterns book, Visitor patterns have a Composite Association between the Host and the Visitor and therefore the Visitor containing the operations has the same life cycle as the Host.

The Visitor in the Visitor Pattern must be aware of all Hosts and provide operations for each. The Visitor controls the operations run by the Host. This means that if your object model changes regularly, it will involve additional maintenance if a Visitor Pattern is used.

The Strategy in the Strategy Pattern will provide only the required algorithms, and the decision over which operation is executed is handled by the Context. Therefore the Context in the Strategy Pattern must be aware of the operations on the Strategy. If a Context is added, there is less maintenance (providing a new algorithm is not required).

Use the Visitor Pattern When:
  • An object structure will not change often, but operations across them will.
  • You have specific related functionality for each concrete class, and wish to encapsulate it.
  • Operation requires data that the Object shouldn't know about.
  • You wish to maintain state within operations across multiple objects.
Example:
  • An application may change its "Skin" which will alter the way controls are drawn. The code for deciding how controls are drawn could be encapsulated in Visitor implementations. Each control will require a separate operation.

Use the Strategy Pattern When:
  • A few algorithms will be used by many different classes.
  • Different algorithms may be used by a class at different times.
  • Operation requires data that the Object shouldn't know about.
  • Classes are using multiple conditional statements. These can be moved to an implementation of the Strategy class.
  • An object structure is likely to change often.
Example:
  • Different methods of calculating interest and fees will be used by clients of a bank. These algorithms can be encapsulated in Strategy implementations and associated with individual clients at runtime.

I am interested in getting feedback on these conclusions. Please let me know if you think anything I have written in incorrect, or you can think of better examples or ways to clarify things.

4 Comments:

Blogger paul said...

Three important things I've found when trying to learn patterns are:
- the only real way for them to sink in is to actually use them
- the real value of many structural patterns (visitor/strategy, for example) is in seeing how they adapt to changes in your code
- possibly most importantly - don't start trying to apply patterns to each problem you see. It's better to write the simplest code possible and teach yourself to notice when a pattern is emerging in that code.

That said, heres my take on each:

The key to the visitor pattern is that it encapsulates operations that belong outside the heirarchy of objects. Operations can be added without modifying the visitees' interfaces.

This is a tidy way of avoiding the pollution of your model interface with logic-type code. In your example, your controls are visitable not only to rendering classes, but to anything that extends your base visitor class.

The secondary value here is that your code will be robust in the face of change, because it will break at compile time (which is good.) If you add another visitee class (a new control to be skinned, in your example) all of your visitors (your skin renderers) will fail to compile until you tell them how to handle this new control.

Funnily enough just last week I read an article by Scott Meyers on the first moment he realised what the Visitor pattern was for.

I disagree slightly with your description of a strategy. There is no real need for the 'context' to be part of a heirarchy - it need only be aware of the interface of the strategy it is implementing. The strategy class, however, is best envisioned as a heirarchy of behavioural based classes.

I find the best value in strategy (or policy) patterns is that they allow the tailoring of the behaviour of your 'context' class after you've written it (at runtime, even!)

I find Strategies are particularly useful in library based code, when creating utility classes that are in effect a template that you would like to tailor separately each time you use them. For example, you might want to use your control classes in different programs, some multithreaded, and using different rendering engines or platforms. If you had a threading strategy class defined as so:

class ThreadingStrategyBase
{
void Lock() = 0;
void Unlock() = 0;
};

and a rendering strategy thusly:

class RenderingStrategyBase
{
void SetWindowText( Text ) = 0;
void Render( DeviceContext ) = 0;
};

and your Control was written as such:
class ControlBase
{
ControlBase(
ThreadingStrategyBase&,
RenderingStrategyBase& );
};

Then all of a sudden you've separated the threading/rendering concerns out of your Controls. This shows the power of combining orthogonal strategies in the one class - with these two strategy types, if you had two concrete threading implementations and three concrete rendering implementations, then you would have up to 6 different usable incarnations of your Control class...

Andrei Alexandrescu showed how Policy/Strategy patterns could be used to good effect in libraries in his book Modern C++ design patterns... it is very template heavy and definitely for experienced C++ users however, so caveat emptor.

10:37 AM  
Blogger Leedrick said...

Thanks for your input Paul. I agree with your comment on the contexts not needing to be part of a hierarchy, and I have changed the text to read "contexts are related classes".

An important point you raised is that the key to understanding the difference between these patterns (GOF calls Visitor and Strategy behavioural, unlike the Bridge pattern which it calls structural) is to see how they adapt to change. This reveals the biggest differences between these two patterns.

The fact that a missing operation specific to a new "Visitee" will be determined at compile time is also and important point. That said, I don't think it is something that will help you decide which pattern to use. It's more of a nice side effect.

I agree with Scott Meyers that Visitor is not a great name for the pattern. I can't come up with a better one right now though:)

Right now I have the G.O.F. Design Patterns book, and the power of the Internets for reference. Some more good examples of the usage of the various patterns would be nice, but I'd probably look for c# based examples. I can read c++, but I certainly wouldn't call myself an experienced user.

4:24 PM  
Blogger paul said...

Well, if you think about it the visitor pattern is a single operation that is designed to be applied to an entire family of contexts.

A strategy is a collection of related operations that work under a single context, and indeed have no real knowledge of the interface of their context at all.

A strategy is almost the opposite of a visitor, I guess, in that you change your context to use different strategies. New strategies may be written over time, but the interface for a strategy is resistant to change (you'd need to rewrite a lot of code that uses it.) Visitors are designed to be extended over time, and the operations they perform can be changed without worrying about the context in which they are running.

I'm late for work, can't proof this so hopefully it's understandable.

12:55 AM  
Blogger Thang said...

I think visitor and strategy patterns are to solve different problems.

1) Strategy pattern works under a context while visitor pattern doesn't need a context to execute its methods. Context object is a very important key point when using Strategy pattern. Remember that client executes Strategy through method execute() of a context it has. The context abstracts away which strategy is executed from client perspective. Context class makes the model become many-many relationship. Same strategy can be used in different contexts and different contexts can be used the same strategy.

2) Visitor pattern must know visitee's interface and what kind of data it expects from visitee. Strategy doesn't need to understand any interface. Strategy can potentially access many resources from different places to perform its execution. In this way, Strategy pattern deals with a larger scope than visitor pattern does.

3:27 PM  

Post a Comment

Links to this post:

Create a Link

<< Home