CSC/ECE 517 Fall 2010/ch3 3h PW

From Expertiza_Wiki
Jump to navigation Jump to search

The Strategy pattern in static and dynamic languages

What is the Strategy Pattern

The strategy design pattern is a type of object oriented design pattern. The main purpose of the strategy pattern is to separate out the object from how it behaves so that it can be changed at runtime. To do this, we remove inheritance from the code and extract out the behaviors, or algorithms, that are different between otherwise similar objects of a particular type. We then encapsulate each of the different algorithms into a separate class, closure, or module, called a strategy. At runtime, the object's behavior is chosen and specified, and can be easily modified according to factors that may not be known when creating the classes.

Should I use the Strategy Pattern

There are some advantages to using the strategy design pattern over other design options.

> Each of the different behaviors, or algorithms, of an object can be managed in separate classes/closures/modules, bringing clarity and separation to the code. By pulling out the algorithms, the behaviors are easier to find and identify.

> Normally, when using multiple similar objects that are different only in the behaviors they have, the application would have required many separate classes with a lot of code duplication. Using the strategy pattern, the code could be reduced down to a single class using several strategies.

> Also, when the behaviors need to be modified, they only need to be modified in one place as opposed to the number of classes where the behavior is used.


There is also a disadvantage of choosing to use the strategy pattern.

> More objects have to be created if you encapsulate each algorithm in its own strategy, possibly making a simple program more complex.


For smaller programs that have no code duplication and no chance of reusing the algorithms, the strategy pattern may make the code more difficult to read and provide few benefits. However, for larger programs and programs with duplication or reusable algorithms, implementing the strategy pattern could be beneficial.

Class Diagram Supporting the Strategy Design Pattern

The following drawing supports the examples below.


How to Implement in a Static Language

The static languages implement the strategy pattern by creating a class and inside of that class is a reference to an action or algorithm class. This action class is usually a superclass, that way one of the actual types of actions to be perform can be assigned. These actions are essentially subclasses of the action class. In java, we can use the abstract type interface which means any class that implements it must define each of the abstract functions. In the example below, the main object is the BasketballPlayer class. In that class is a reference to the Action interface. The Action interface has an abstract method perform. There are 3 classes that implement the Action class. They are Pass, Dribble, and Shoot. Each of these methods defines the method perform and then does that action. The BasketballPlayerExample class shows how to instantiate the BasketballPlayer class and then define different actions for it.

Examples

C++ Code Example

#include <iostream>
using namespace std;

class Action
{
    public:
        virtual void perform() const = 0;
};
 
class Dribble: public Action
{
    public:
        virtual void perform() const
        {
            cout << "Player is dribbling" << endl;
        }
};
 
class Pass: public Action
{
    public:
        virtual void perform() const
        {
            cout << "Player is passing" << endl;
        }
};
 
class Shoot: public Action
{
    public:
        virtual void perform() const
        {
            cout << "Player is shooting" << endl;
        }
};
 
class BasketballPlayer
{
    private:
        Action * action_m;
 
    public:
        explicit BasketballPlayer(Action *action)
        {
	     action_m = action;
        }
 
        void set_strategy(Action *action)
        {
            action_m = action;
        }
 
        void perform() const
        {
            action_m->perform();
        }
};

 
int main(int argc, char *argv[])
{

    Dribble dribble;
    Shoot shoot;
    Pass pass;
 
    BasketballPlayer bballOne(&dribble);
    BasketballPlayer bballTwo(&shoot);
    BasketballPlayer bballThree(&pass);

    bballOne.perform(); //Prints "Player is dribbling"
    bballTwo.perform(); //Prints "Player is passing"
    bballThree.perform(); //Prints "Player is shooting"
 
    bballOne.set_strategy(&pass);
    bballOne.perform(); //Prints "Player is passing"
    bballOne.set_strategy(&shoot);
    bballOne.perform(); //Prints "Player is shooting"
 
    return 0;
}

Java Code Example

interface Action {
    void perform();
}
 
// Implements the algorithm using the strategy interface
class Dribble implements Action {
 
    public void perform() {
        System.out.println("Player is dribbling");
    }
}
 
class Pass implements Action {
 
     public void perform() {
        System.out.println("Player is passing");
    }
}
 
class Shoot implements Action {
 
    public void perform() {
        System.out.println("Player is shooting");
    }    
}
 
// Configured with a ConcreteStrategy object and maintains a reference to a Strategy object
class BasketballPlayer {
 
    private Action action;
 
    // Constructor
    public BasketballPlayer(Action action) {
        this.action = action;
    }
 
    public void perform() {
        action.perform();
    }
}
//BasketballPlayerExample test application
 
class BasketballPlayerExample {
 
    public static void main(String[] args) {
 
        BasketballPlayer bballPlayer;
 
        // Three contexts following different strategies
        bballPlayer = new BasketballPlayer(new Dribble());
        bballPlayer.perform();
 
        bballPlayer = new BasketballPlayer(new Pass());
        bballPlayer.perform();
 
        bballPlayer = new BasketballPlayer(new Shoot());
        bballPlayer.perform();
    }
}

How to Implement in a Dynamic Language

Dynamic languages implement the strategy pattern by extracting out the different behaviors of the main action class into individual classes or modules. In the example below, the main object is the BasketballPlayer class. For this example, modules are used to encapsulate the three actions. They are Pass, Dribble, and Shoot. Each of these modules defines the method perform() and then does that action. The instantiations at the end show how to create the BasketballPlayer objects and use them to perform the different actions defined in the modules.

Examples

Ruby code

#Class to be instantiated for use in the Strategy pattern
class BasketballPlayer
  def initialize(&action)
    @action = action
  end
  def perform 
    @move.call
  end
  def method_missing(method_name)
    puts "#{action} is not a known b-ball play"
  end
end

#The Strategies - the modules that implement the different behaviors that our object would use
module Pass
  def pass
    puts"Player is passing!"
  end
end

module Dribble
  def dribble
    puts "Player is dribbling!"
  end
end

module Shoot
  def shoot
    puts "Player is shooting!"
  end 
end

#Lets test the strategy pattern
bballPlayer = BasketballPlayer.new(dribble)
offense.perform #=>Player is dribbling!

bballPlayer = BasketballPlayer.new(pass)
defense.perform #=>Player is passing!

bballPlayer = BasketballPlayer.new(shoot)
offense.perform #=>Player is passing!
...

Ruby Code using Blocks

#Class to be instantiated for use of the Strategy Pattern
class BasketballPlayer
  def initialize(&action)
    @action = action
  end
 
  def perform
    @action.call
  end
end

#Lets test the strategy pattern
#By using blocks of code, classes or modules do not need to be created and the methods do not need to be declared.
a = BasketballPlayer.new { puts 'Player is passing!' }
a.perform #=> Player is passing!

b = BasketballPlayer.new { puts 'Player is dribbling!' }
b.execute #=> Player is dribbling!

c = BasketballPlayer.new { puts 'Player is shooting!' }
c.perform #=> Player is shooting!


In comparing the two Ruby examples, we see that there is more than one way to implement the strategy pattern. In the former example, each algorithm is encapsulated into a method in different modules. Whereas, in the latter example, blocks are used to define the algorithms. For this example, both methods work well, and the latter one is more straightforward. Not having the create methods for each algorithm shortens the length of the code. However, for larger projects, where the algorithms may need to be used for more objects than one, the former example is more useful because the methods are implemented and can be reused. Also, if the algorithms need to be tweaked along the way, having each behavior in a single place can improve maintenance ease.


Groovy Example

//Class used to instantiate the Strategy Pattern
class BasketballPlayer{
  def action

  BasketballPlayer(action) {
    this.action = action
  }

  def perform() {
    action()
  }
}

//Test the strategy pattern
//In Groovy, we use closures to pass in the action through the instance instantiation.
def bballPlayerA = new BasketballPlayer({ println 'Player is passing!' })
bballPlayerA.perform() //=> Player is passing!
def bballPlayerB = new BasketballPlayer({ println 'Player is dribbling!' })
bballPlayerB.perform() //=> Player is dribbling!
def bballPlayerC = new BasketballPlayer({ println 'Player is shooting!' })
bballPlayerC.perform() //=> Player is shooting!

This example is very similar to the latter Ruby example in that the action to be performed is simply passed in to the instance of the BasketballPlayer object at runtime. Groovy uses closures to do this.


Python Example

View the Python example and see the first implementation of the strategy pattern in Python. With that first example, we see basically the same design as with Ruby. We have a basic class called Dice, which will be our main class for creating new objects. Then a DiceStrategy class is created to be the base strategy class, or superclass. The main method, roll(), which is to be used for the strategy behavior, is not implemented in this class. The two concrete strategy classes DiceStrategy1 and DiceStrategy2 are created and they each implement the roll() method with a different behavior.

Many of the dynamic languages use first class functions such as closures or blocks, to simply pass in the behavior without actually defining it in a method. This style reduces the obviousness of the use of the strategy pattern because the methods are not encapsulated in a method somewhere. However, they are defined away from the main class and allow for the behaviors to be changed at runtime, which is the main purpose of the strategy pattern.

Static vs Dynamic Implementations

As seen with the examples given, static and dynamic implementations of the strategy method are very similar and both have similar benefits, such as the ability to easily swap out behaviors during run-time. When comparing the Java example and the first Ruby example, we see even more similarities in the benefits, like reducing code duplication and improving code maintainability. Furthermore, the interchangeable behaviors are easily identified in those two examples.

With static implementations, interfaces or inheritance, must be used to create the context interface. Also, a SuperClass or an interface has to be created. This Superclass or interface also has to define virtual methods to be used to match what will be called from the Object class that will reference them. Then the context classes must be created by inheriting from the SuperClass or extending the class defined as an Interface and also implementing the virtual methods.

With dynamic languages, such as Ruby, Groovy, or Python, interfaces are not used. Implementation only requires a base context class and the strategy classes. Furthermore, using dynamic languages, strategy classes do not even have to be used, you could use strategy modules, closures, or blocks in place of them. This will require much fewer lines of code, less classes to define, and less memory when running the program. If using closures or blocks, a programmer using a dynamic language could avoid creating a Superclass and defining the virtual methods then implementing them, instead a block could just be passed in.

Strategy Pattern Compared to State Pattern

Another design pattern that is in use is the state pattern and it is actually very similar to the strategy pattern. With the state pattern, the object's actions or algorithm still can be changed and it is created in a separate class. The difference is that the state will change over time and as it changes the behavior automatically changes as well. So, with the State pattern the behavior changing is done automatically, and the Strategy pattern requires the client that created the object to assign a new action or behavior.

References

[1] Eric Freeman, Elisabeth Freeman, Kathy Sierra, Bert Bates; 'Head First Design Patterns', O'Reilly, 2004

[2] Wikipedia, the free encyclopedia: Strategy Pattern, 2010. Wikimedia Foundation, Inc.: http://en.wikipedia.org/wiki/Strategy_pattern

[3] Antonio García; Java Resources: The Strategy Design Pattern: http://www.exciton.cs.rice.edu/JavaResources/DesignPatterns/StrategyPattern.htm

[4] Wikipedia, the free encyclopedia: Interface(Java), 2010. Wikimedia Foundation, Inc.: http://en.wikipedia.org/wiki/Interface_%28Java%29

[5] Steven F. Lott; Building Skills in Python, Chapter 23 Design Patterns, 2008.: http://www.linuxtopia.org/online_books/programming_books/python_programming/python_ch23s03.html

[6] Design Patterns: Simply, Strategy Design Patterns: http://sourcemaking.com/design_patterns/strategy

[7] Wikipedia, the free encyclopedia: First-class function, 2010. Wikimedia Foundation, Inc.: http://en.wikipedia.org/wiki/First-class_function

[8] Andreas Viklund; Groovy - Strategy, Codehaus Foundation, 2008: http://groovy.codehaus.org/Strategy+Pattern