Strategy
Strategy
Intent
Define a family of algorithms, encapsulate each one, and make them interchange-
able. Strategy lets the algorithm vary independently from clients that use it.
Also Known As
Policy
Motivation
Many algorithms exist for breaking a stream of text into lines. Hard-wiring all such
algorithms into the classes that require them isn't desirable for several reasons:
• Clients that need linebreaking get more complex if they include the line-
breaking code. That makes clients bigger and harder to maintain, especially
if they support multiple linebreaking algorithms.
• Different algorithms will be appropriate at different times. We don't want to
support multiple linebreaking algorithms if we don't use them all.
• It's difficult to add new algorithms and vary existing ones when linebreaking
is an integral part of a client.
We can avoid these problems by defining classes that encapsulate different line-
breaking algorithms. An algorithm that's encapsulated in this way is called a
strategy.
Applicability
Use the Strategy pattern when
• many related classes differ only in their behavior. Strategies provide a way
to configure a class with one of many behaviors.
• you need different variants of an algorithm. For example, you might de-
fine algorithms reflecting different space/time trade-offs. Strategies can be
used when these variants are implemented as a class hierarchy of algo-
rithms [HO87].
• an algorithm uses data that clients shouldn't know about. Use the Strategy
pattern to avoid exposing complex, algorithm-specific data structures.
• a class defines many behaviors, and these appear as multiple conditional
statements in its operations. Instead of many conditionals, move related
conditional branches into their own Strategy class.
Structure
STRATEGY 317
Participants
• Strategy (Compositor)
- declares an interface common to all supported algorithms. Context uses
this interface to call the algorithm defined by a ConcreteStrategy.
• ConcreteStrategy (SimpleCompositor, TeXCompositor, ArrayCompositor)
- implements the algorithm using the Strategy interface.
• Context (Composition)
- is configured with a ConcreteStrategy object.
- maintains a reference to a Strategy object.
- may define an interface that lets Strategy access its data.
Collaborations
• Strategy and Context interact to implement the chosen algorithm. A context
may pass all data required by the algorithm to the strategy when the algorithm
is called. Alternatively, the context can pass itself as an argument to Strategy
operations. That lets the strategy call back on the context as required.
• A context forwards requests from its clients to its strategy. Clients usually
create and pass a ConcreteStrategy object to the context; thereafter, clients
interact with the context exclusively. There is often a family of ConcreteStrategy
classes for a client to choose from.
Consequences
The Strategy pattern has the following benefits and drawbacks:
1. Families of related algorithms. Hierarchies of Strategy classes define a family of
algorithms or behaviors for contexts to reuse. Inheritance can help factor out
common functionality of the algorithms.
2. An alternative to subclassing. Inheritance offers another way to support a
variety of algorithms or behaviors. You can subclass a Context class directly
to give it different behaviors. But this hard-wires the behavior into Context. It
mixes the algorithm implementation with Context's, making Context harder
to understand, maintain, and extend. And you can't vary the algorithm
dynamically. You wind up with many related classes whose only difference
is the algorithm or behavior they employ. Encapsulating the algorithm in
separate Strategy classes lets you vary the algorithm independently of its
context, making it easier to switch, understand, and extend.
3. Strategies eliminate conditional statements. The Strategy pattern offers an alter-
native to conditional statements for selecting desired behavior. When differ-
ent behaviors are lumped into one class, it's hard to avoid using conditional
318 BEHAVIORAL PATTERNS CHAPTER 5
The Strategy pattern eliminates this case statement by delegating the line-
breaking task to a Strategy object:
void Composition::Repair () {
_compositor->Compose();
// merge results with existing composition, if necessary
}
object. Shared strategies should not maintain state across invocations. The
Flyweight (195) pattern describes this approach in more detail.
Implementation
Consider the following implementation issues:
1. Defining the Strategy and Context interfaces. The Strategy and Context interfaces
must give a ConcreteStrategy efficient access to any data it needs from a
context, and vice versa.
One approach is to have Context pass data in parameters to Strategy
operations—in other words, take the data to the strategy. This keeps Strategy
and Context decoupled. On the other hand, Context might pass data the
Strategy doesn't need.
Another technique has a context pass itself as an argument, and the strategy
requests data from the context explicitly. Alternatively, the strategy can store
a reference to its context, eliminating the need to pass anything at all. Either
way, the strategy can request exactly what it needs. But now Context must
define a more elaborate interface to its data, which couples Strategy and
Context more closely.
The needs of the particular algorithm and its data requirements will deter-
mine the best technique.
2. Strategies as template parameters. In C++ templates can be used to configure
a class with a strategy. This technique is only applicable if (1) the Strategy
can be selected at compile-time, and (2) it does not have to be changed at
run-time. In this case, the class to be configured (e.g., Context) is defined
as a template class that has a Strategy class as a parameter:
template <class AStrategy>
class Context {
void Operation() { theStrategy.DoAlgorithm(); }
// . . .
private:
AStrategy theStrategy;
};
The class is then configured with a Strategy class when it's instantiated:
class MyStrategy {
public:
void DoAlgorithm();
};
Context<MyStrategy> aContext;
With templates, there's no need to define an abstract class that defines the
interface to the Strategy. Using Strategy as a template parameter also
lets you bind a Strategy to its Context statically, which can increase
efficiency.
320 BEHAVIORAL PATTERNS CHAPTERS
3. Making Strategy objects optional. The Context class may be simplified if it's
meaningful not to have a Strategy object. Context checks to see if it has
a Strategy object before accessing it. If there is one, then Context uses it
normally. If there isn't a strategy, then Context carries out default behavior.
The benefit of this approach is that clients don't have to deal with Strategy
objects at all unless they don't like the default behavior.
Sample Code
We'll give the high-level code for the Motivation example, which is based on the
implementation of Composition and Compositor classes in Interviews [LCI+92].
The Composition class maintains a collection of Component instances, which
represent text and graphical elements in a document. A composition arranges
component objects into lines using an instance of a Compositor subclass, which
encapsulates a linebreaking strategy. Each component has an associated natural
size, stretchability, and shrinkability. The stretchability defines how much the
component can grow beyond its natural size; shrinkability is how much it can
shrink. The composition passes these values to a compositor, which uses them to
determine the best location for linebreaks.
class Composition {
public:
Composition(Compositor*);
void Repair();
private:
Compositor* _compositor;
Component* _components; // the list of components
int _componentCount; // the number of components
int _lineWidth; // the Composition's line width
int* _lineBreaks; // the position of linebreaks
// in components
int _lineCount; // the number of lines
When a new layout is required, the composition asks its compositor to determine
where to place linebreaks. The composition passes the compositor three arrays
that define natural sizes, stretchabilities, and shrinkabilities of the components. It
also passes the number of components, how wide the line is, and an array that
the compositor fills with the position of each linebreak. The compositor returns
the number of calculated breaks.
The Compositor interface lets the composition pass the compositor all the infor-
mation it needs. This is an example of "taking the data to the strategy":
STRATEGY 321
class Compositor {
public:
virtual int Compose(
Coord natural[], Coord stretch[], Coord shrinkf],
int componentCount, int lineWidth, int breaks[]
) = 0;
protected:
Compositor();
};
These classes don't use all the information passed in Compose. SimpleCom-
positor ignores the stretchability of the components, taking only their nat-
ural widths into account. TeXCompositor uses all the information passed to it,
whereas ArrayCompositor ignores everything.
To instantiate Compos it ion, you pass it the compositor you want to use:
Known Uses
Both ET++ [WGM88] and Interviews use strategies to encapsulate different line-
breaking algorithms as we've described.
STRATEGY 323
In the RTL System for compiler code optimization [JML92], strategies define differ-
ent register allocation schemes (RegisterAllocator) and instruction set scheduling
policies (RISCscheduler, CISCscheduler). This provides flexibility in targeting the
optimizer for different machine architectures.
The ET++SwapsManager calculation engine framework computes prices for dif-
ferent financial instruments [EG92]. Its key abstractions are Instrument and Yield-
Curve. Different instruments are implemented as subclasses of Instrument. Yield-
Curve calculates discount factors, which determine the present value of future
cash flows. Both of these classes delegate some behavior to Strategy objects. The
framework provides a family of ConcreteStrategy classes for generating cash
flows, valuing swaps, and calculating discount factors. You can create new cal-
culation engines by configuring Instrument and YieldCurve with the different
ConcreteStrategy objects. This approach supports mixing and matching existing
Strategy implementations as well as defining new ones.
The Booch components [BV90] use strategies as template arguments. The Booch
collection classes support three different kinds of memory allocation strategies:
managed (allocation out of a pool), controlled (allocations/deallocations are pro-
tected by locks), and unmanaged (the normal memory allocator). These strategies
are passed as template arguments to a collection class when it's instantiated. For
example, an UnboundedCollection that uses the unmanaged strategy is instanti-
ated as UnboundedCollection<MyItemType*, Unmanaged>.
RApp is a system for integrated circuit layout [GA89, AG90]. RApp must lay out
and route wires that connect subsystems on the circuit. Routing algorithms in
RApp are defined as subclasses of an abstract Router class. Router is a Strategy
class.
Borland's Object Windows [Bor94] uses strategies in dialogs boxes to ensure that
the user enters valid data. For example, numbers might have to be in a certain
range, and a numeric entry field should accept only digits. Validating that a string
is correct can require a table look-up.
ObjectWindows uses Validator objects to encapsulate validation strategies. Val-
idators are examples of Strategy objects. Data entry fields delegate the validation
strategy to an optional Validator object. The client attaches a validator to a field
if validation is required (an example of an optional strategy). When the dialog is
closed, the entry fields ask their validators to validate the data. The class library
provides validators for common cases, such as a Range Validator for numbers.
New client-specific validation strategies can be defined easily by subclassing the
Validator class.
Related Patterns
Flyweight (195): Strategy objects often make good flyweights.