Strategy Pattern
Definition
The Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one, and allows them to be interchangeable within that family. This flexibility enables the selection of an algorithm at runtime based on current needs.
Structure
classDiagram
class Context {
-Strategy strategy
+setStrategy(Strategy s)
+executeStrategy()
}
class Strategy {
<<interface>>
+algorithmInterface()
}
class ConcreteStrategyA {
+algorithmInterface()
}
class ConcreteStrategyB {
+algorithmInterface()
}
class ConcreteStrategyC {
+algorithmInterface()
}
Context o-- Strategy : uses
Strategy <|-- ConcreteStrategyA : implements
Strategy <|-- ConcreteStrategyB : implements
Strategy <|-- ConcreteStrategyC : implements
Participants
- Strategy: Declares a common interface for all algorithms.
- ConcreteStrategy: Implements specific algorithms defined by the Strategy interface.
- Context: Maintains a reference to a Strategy and delegates the execution of the algorithms.
Characteristics
The Context class is used by clients to hold a reference to the Strategy. Algorithms can dynamically be interchanged by switching the ConcreteStrategy assigned to the Strategy.
Example
Consider the "SimUDuck" example from the "Head First Design Patterns" book. Ducks exhibit both common and unique behaviors. By utilizing the Strategy Pattern, different flying and quacking behaviors can be assigned without altering the Duck class.
Implementation of the SimUDuck in C#
classDiagram
class Duck {
<<abstract>>
-IFlyBehavior flyBehavior
-IQuakBehavior quackBehavior
+setFlyBehavior()
+setQuaukBehavior()
+swim()
+display()
+performQuack()
+performFly()
}
class MallardDuck {
+display()
}
class RedheadDuck {
+display()
}
class RubberDuck {
+display()
}
class DecoyDuck {
+display()
}
class ModelDuck {
+display()
}
class IFlyBehavior {
<<interface>>
+fly()
}
class FlyWithWings {
+fly()
}
class FlyNoWay {
+fly()
}
class FlyRocketPowered {
+fly()
}
class IQuackBehavior {
<<interface>>
+quack()
}
class QuackBehavior {
+quack()
}
class SqueakBehavior {
+quack()
}
class MuteQuackBehavior {
+quack()
}
class FakeQuackBehavior {
+quack()
}
Duck o-- IFlyBehavior : uses
Duck o-- IQuackBehavior : uses
Duck <|-- MallardDuck : implements
Duck <|-- RedheadDuck : implements
Duck <|-- RubberDuck : implements
Duck <|-- DecoyDuck : implements
Duck <|-- ModelDuck : implements
IFlyBehavior <|-- FlyWithWings : implements
IFlyBehavior <|-- FlyNoWay : implements
IFlyBehavior <|-- FlyRocketPowered : implements
IQuackBehavior <|-- QuackBehavior : implements
IQuackBehavior <|-- SqueakBehavior : implements
IQuackBehavior <|-- MuteQuackBehavior : implements
IQuackBehavior <|-- FakeQuackBehavior : implements
- Strategy: IFlyBehavior, IQuackBehavior
- ConcreteStrategy: FlyWithWings, FlyNoWay, FlyRocketPowered, QuackBehavior, SqueakBehavior, MuteQuackBehavior, FakeQuackBehavior
- Context: Duck
In this setup, different ducks can have behaviors assigned dynamically based on their characteristics. For example, Mallard and Redhead Ducks can fly and quack, therefore they use FlyWithWings and QuackBehavior. Rubber Ducks, which cannot fly but can make a squeaking sound, are therefore assigned FlyNoWay and SqueakBehavior. Decoy Ducks neither fly nor make sounds, hence they are assigned FlyNoWay and MuteQuackBehavior. Model Ducks, unable to fly but capable of quacking, are assigned FlyNoWay and QuackBehavior. The use of the Strategy Pattern allows for these behaviors to be changed without altering the Duck class itself.
Code Overview
The source code below in C# illustrates the implementation of the Strategy Pattern.
Each duck type inherits from the abstract Duck class and can exhibit various flying and quacking behaviors determined by interfaces IFlyBehavior and IQuackBehavior. The Duck class delegates flying and quacking actions to objects that implement these interfaces, which can be swapped dynamically.
// Base Duck class (Context) encapsulating behavior interfaces
public abstract class Duck(IFlyBehavior flyBehavior, IQuackBehavior quackBehavior)
{
// Each duck has a different display method
public abstract void Display();
// Dynamically change flying behavior
public void SetFlyBehavior(IFlyBehavior fb) => flyBehavior = fb;
// Dynamically change quacking behavior
public void SetQuackBehavior(IQuackBehavior qb) => quackBehavior = qb;
// Delegate the behavior
public void PerformFly()
{
flyBehavior.Fly();
}
// Delegate the behavior
public void PerformQuack()
{
quackBehavior.Quack();
}
public void Swim() => Console.WriteLine("All ducks float, even decoys!");
}
public class MallardDuck() : Duck(new FlyWithWings(), new QuackBehavior())
{
public override void Display() => Console.WriteLine("I'm a real Mallard duck");
}
public class RedheadDuck() : Duck(new FlyWithWings(), new QuackBehavior())
{
public override void Display() => Console.WriteLine("I'm a real Red Headed duck");
}
public class RubberDuck() : Duck(new FlyNoWay(), new SqueakBehavior())
{
public override void Display() => Console.WriteLine("I'm a rubber duck");
}
public class DecoyDuck() : Duck(new FlyNoWay(), new MuteQuackBehavior())
{
public override void Display() => Console.WriteLine("I'm a decoy duck");
}
public class ModelDuck() : Duck(new FlyNoWay(), new QuackBehavior())
{
public override void Display() => Console.WriteLine("I'm a model duck");
}
// flying behavior interface
public interface IFlyBehavior
{
void Fly();
}
// quacking behavior interface
public interface IQuackBehavior
{
void Quack();
}
Different behaviors are implemented by concrete classes that conform to IFlyBehavior and IQuackBehavior. This allows changing a duck's behavior at runtime by injecting different behavior instances.
// Concrete flying behaviors
public class FlyWithWings : IFlyBehavior
{
public void Fly() => Console.WriteLine("I'm flying");
}
public class FlyNoWay : IFlyBehavior
{
public void Fly() => Console.WriteLine("I cannot fly");
}
public class FlyRocketPowered : IFlyBehavior
{
public void Fly() => Console.WriteLine("I'm flying with a rocket!");
}
// Concrete quacking behaviors
public class QuackBehavior : IQuackBehavior
{
public void Quack() => Console.WriteLine("Quack");
}
public class SqueakBehavior : IQuackBehavior
{
public void Quack() => Console.WriteLine("Squeak");
}
public class MuteQuackBehavior : IQuackBehavior
{
public void Quack() => Console.WriteLine("<<Silence>>");
}
public class FakeQuackBehavior : IQuackBehavior
{
public void Quack() => Console.WriteLine("Qwak");
}
// Client
Duck mallardDuck = new MallardDuck();
mallardDuck.Display();
mallardDuck.PerformQuack();
mallardDuck.PerformFly();
/*
I'm a real Mallard duck
I'm flying
Quack
*/
Duck modelDuck = new ModelDuck();
modelDuck.Display();
modelDuck.PerformFly();
modelDuck.PerformQuack();
/*
I'm a model duck
I cannot fly
Quack
*/
Duck decoyDuck = new DecoyDuck();
decoyDuck.Display();
decoyDuck.PerformFly();
decoyDuck.PerformQuack();
/*
I'm a decoy duck
I cannot fly
<<Silence>>
*/
Duck rubberDuck = new RubberDuck();
rubberDuck.Display();
rubberDuck.PerformFly();
rubberDuck.PerformQuack();
/*
I'm a rubber duck
I cannot fly
Squeak
*/
Duck duck = new MallardDuck();
duck.Display();
duck.PerformFly();
duck.PerformQuack();
/*
I'm a real Mallard duck
I'm flying
Quack
*/
// Changing behavior dynamically
duck.SetFlyBehavior(new FlyRocketPowered());
duck.PerformFly();
/*
I'm flying with a rocket!
*/
duck.SetQuackBehavior(new FakeQuackBehavior());
duck.PerformQuack();
/*
Qwak
*/
This example demonstrates how the Strategy Pattern allows for flexible and interchangeable behaviors within objects without modifying the objects themselves. By separating behavior implementation from the client classes, we enhance modularity and extendibility in software design, adhering to the open/closed principle.
Application
ViewResolver in Spring MVC
The Strategy Pattern is effectively utilized in the ViewResolver component of Spring MVC, which facilitates dynamic view resolution based on request parameters. This setup allows Spring MVC to support various rendering technologies seamlessly and adaptively.
Implementation in Spring MVC
The DispatcherServlet serves as the context in this pattern, managing an array of ViewResolver strategies. Each ViewResolver is designed to resolve view names into actual views, based on the incoming request details such as view name and locale.
Here's a simplified class diagram that illustrates the structure:
classDiagram
class DispatcherServlet {
-viewResolvers
}
class ViewResolver {
+resolveViewName()
}
class AbstractCachingViewResolver {
+resolveViewName()
}
ViewResolver o-- DispatcherServlet: uses
ViewResolver <|-- AbstractCachingViewResolver : implements
AbstractCachingViewResolver <|-- UrlBasedViewResolver : implements
AbstractCachingViewResolver <|-- ThymeleafViewResolver : implements
UrlBasedViewResolver <|-- InternalResourceViewResolver : implements
UrlBasedViewResolver <|-- AbstractTemplateViewResolver : implements
AbstractTemplateViewResolver <|-- FreeMarkerViewResolver: implements
- Context: DispatcherSerlvet
- Strategy: ViewResolver
- Concrete Strategy: InternalResourceViewResolver, FreeMarkerViewResolver, ThymeleafViewResolver, …
This structure allows the DispatcherServlet to map requests to appropriate ViewResolvers, such as InternalResourceViewResolver, FreeMarkerViewResolver, and ThymeleafViewResolver. Each implements the resolveViewName method differently to cater to specific needs.
Code Snippet from Spring Framework
Below is a Java code snippet demonstrating the dynamic strategy selection in action within the DispatcherServlet:
public interface ViewResolver {
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
public class DispatcherServlet extends FrameworkServlet {
// Array of ViewResolver strategies
@Nullable
private List<ViewResolver> viewResolvers;
// Resolves a view based on the view name and locale
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
//...
}
In this implementation, the DispatcherServlet checks each registered ViewResolver to find a suitable view that matches the request criteria. If a valid view is found, it is returned and rendered. This demonstrates the Strategy Pattern's principle of separating the decision-making process from the execution, thus enabling high modularity and easy interchangeability of strategies.