Allows a family of algorithms to be defined while encapsulating each one. The algorithms can vary independently from clients using them.
Behavior
The context forwards requests from its clients to its strategy: A client creates the context class and
initializes it (through its constructor) with the required type of strategy. A client invokes a strategy method indirectly by calling a public method on the context object, which in turn calls the correct strategy method through its strategy data member
(pStrategy in the UML diagram).
The Strategy pattern is applied to creating different hashing algorithms. Each hashing algorithm is implemented differently in its own separate class. The Strategy pattern organizes this class hierarchy by allowing a client to be configured dynamically with the required hashing algorithm.
/* Strategy Classes */
/* Abstract Strategy */
class BaseHashStrategy
{
// Constructors/Destructors
public:
BaseHashStrategy() {}
virtual ~BaseHashStrategy() {}
// public interface
public:
virtual int GetHashValue( long lData )
{
// Base implementation comes here
std::cout << "In BaseHashStrategy::GetHashValue"
<< std::endl;
return -1;
}
};
/* Concrete Stategies */
class DivisionHashStrategy : public BaseHashStrategy
{
// Constructors/Destructors
public:
DivisionHashStrategy() {}
virtual ~DivisionHashStrategy() {}
// public interface
public:
int GetHashValue( long lData )
{
// Division implementation comes here
OutputDebugString(" In DivisionHashStrategy::GetHashValue");
return 1;
}
};
class FibonacciHashStrategy : public BaseHashStrategy
{
public:
FibonacciHashStrategy() {}
virtual ~FibonacciHashStrategy() {}
// public interface
public:
int GetHashValue( long lData )
{
// Fibonannce implementation comes here
OutputDebugString(" In FibonacciHashStrategy::GetHashValue");
return 2;
}
};
class MiddleSquareHashStrategy : public BaseHashStrategy
{
public:
MiddleSquareHashStrategy() {}
virtual ~MiddleSquareHashStrategy() {}
// public interface
public:
int GetHashValue( long lData )
{
// Middle Square implementation comes here ...
OutputDebugString(" In MiddleSquareHashStrategy::GetHashValue");
return 3;
}
};
/* Context Class */
#include "BaseHashStrategy.h"
class CHash
{
// Constructors/Destructors
public:
CHash( BaseHashStrategy* pBHS ) : m_pBHS( pBHS ) {}
virtual ~CHash()
{
delete m_pBHS;
m_pBHS = NULL;
}
// Public interface
int CalculateHash( long lData)
{
/* Calls GetHashValue() in the class determined by the dynamic type
of m_pBHS. Note that we chose to pass data between a strategy and its
context class using parameters */
return m_pBHS->GetHashValue( lData );
}
private:
/* Holds a reference to the required strategy class. Static type of m_pBHS
is BaseHashStrategy but its dynamic type is that passed in by the client
to CHash constructor Recall: dynamic type decided where virtuals are called */
BaseHashStrategy *m_pBHS;
};
int main(int argc, char* argv[])
{
CHash *pH = new CHash( new DivisionHashStrategy );
int nRet = pH->CalculateHash( 1000 );
// calls DivisionHashStrategy::GetHashValue
delete pH;
return 0;
}
Notes
- Why go through all this when for each strategy we can use something like this in the client:
ConcreteStrategyA *pCSA = new ConcreteStrategyA;
pCSA->AlgorithmA_Method();
ConcreteStrategyB *pCSAB= new ConcreteStrategyB;
pCSB->AlgorithmB_Method();
Reason 1: Adding a new algorithm to the client will make it much more complex.
Reason 2: Different algorithms are appropriate at different times. We can support each
algorithm whenever it is required to do so.
Reason 3: It’s difficult to add new algorithms and vary existing ones where they are integral part of the
client.
- Why did we need to have a Context class when we could subclass Context to contain the required
behavior ? This hard-wires behavior into the Context class making it difficult to vary the algorithm dynamically.
- The Strategy pattern is applied usually when a class defines many behaviors, or when you need different variants of an algorithm (example above), or to avoid exposing complex data that clients need not know about.
- Strategy pattern allows us to ditch conditional if-statements. Consider this CalculateHash():
int CalculateHash( long STRAT_TYPE )
{
if (STRAT_TYPE == FIBONACCI)
// Fibnnoacci implementation here
...
else if (STRAT_TYPE == DIVISION)
// Division implementation here
...
...
}
Conditional statements used to determine how to accomplish something typically indicate the need to use a Strategy pattern.
- The ConcreteStrategy subclasses must have efficient access to any data needed from the context, and vice versa: The context can pass required data in parameters to the strategy operations. Or, the context can pass itself as an argument and the Strategy can use the context to access data directly.