CSC/ECE 517 Fall 2010/ch3 3h ss

From Expertiza_Wiki
Jump to navigation Jump to search

The Strategy Pattern

The Strategy pattern is one of the most common design patterns. Its main idea is to define algorithms into familial groupings. The idea is to encapsulate each algorithm, then make those algorithms interchangeable. The Strategy design pattern allows each of the algorithms to differ autonomously from the various clients that use them.1 This allows those clients to be coupled to an interface not an implementation. At any time, the system can be expanded to allow multiple client implementations without changing the interface. As you can see in the diagram below, the client communicates with the interface, which then chooses the implementation based upon its strategy.4

There are many real life examples of this design pattern in operation today. One that comes to mind would be opening a file. The interface is the Open command of some software. The implementations would be each of the various file types supported by the software. There would be an implementation for text file, an implementation for a comma delimited file, an implementation for a bitmap, and so on.

In the following sections we will compare how the Strategy pattern can be implemented in static and dynamic languages, and then explore if Ruby has an advantage with this design pattern because of its dynamic nature or because of the features of Ruby.

Static Languages

The use of the Strategy design pattern in static languages requires the relations between classes and interfaces to be referenced by extending classes, implementing interfaces, instantiating objects, invoking methods, etc.2 The code in the hyperlinks below show various languages implementing the Strategy design patterns.

Java

Java Strategy design pattern example:

// 1. Define the interface of the algorithm
interface Strategy { public void solve(); }          

// 2. Bury implementation
abstract class TemplateMethod1 implements Strategy { // 3. Template Method 
   public void solve() {
      start();
      while (nextTry() && ! isSolution())
         ;
      stop();
   }
   protected abstract void    start();
   protected abstract boolean nextTry();
   protected abstract boolean isSolution();
   protected abstract void    stop();
}

class Impl1 extends TemplateMethod1 {
   private int state = 1;
   protected void start() {
     System.out.print( "start  " );
   }
   protected void stop() {
     System.out.println( "stop" );
   }
   protected boolean nextTry() {
      System.out.print( "nextTry-" + state++ + "  " );
      return true;
   }
   protected boolean isSolution() {
      System.out.print( "isSolution-" + (state == 3) + "  " );
      return (state == 3);
   }
}

// 2. Bury implementation
abstract class TemplateMethod2 implements Strategy { // 3. Template Method
   public void solve() {                             
      while (true) {
         preProcess();
         if (search()) break;
         postProcess();
      }
   }
   protected abstract void preProcess();
   protected abstract boolean search();
   protected abstract void postProcess();
}

class Impl2 extends TemplateMethod2 {
   private int state = 1;
   protected void    preProcess()  { System.out.print( "preProcess  " ); }
   protected void    postProcess() { System.out.print( "postProcess  " ); }
   protected boolean search() {
      System.out.print( "search-" + state++ + "  " );
      return state == 3 ? true : false;
   }
}

// 4. Clients couple strictly to the interface
public class StrategyDemo {
   public static void clientCode( Strategy strat ) {
     strat.solve();
   }
   public static void main( String[] args ) {
      Strategy[] algorithms = { new Impl1(), new Impl2() };
      for (int i=0; i < algorithms.length; i++) {
         clientCode( algorithms[i] );
      }
   }
}

This code identifies an interface for the related algorithms as shown in section 1, defines classes to implement the individual algorithms as shown in section 2, and shows the client coupling to the interface as shown in section 4.4

This code snippet also makes use of the Template method design pattern.

C++

C++ Strategy design pattern example:

#include <iostream.h>
#include <fstream.h>
#include <string.h>

class Strategy;

class TestBed
{
  public:
    enum StrategyType
    {
        Dummy, Left, Right, Center
    };
    TestBed()
    {
        strategy_ = NULL;
    }
    void setStrategy(int type, int width);
    void doIt();
  private:
    Strategy *strategy_;
};

class Strategy
{
  public:
    Strategy(int width): width_(width){}
    void format()
    {
        char line[80], word[30];
        ifstream inFile("quote.txt", ios::in);
        line[0] = '\0';

        inFile >> word;
        strcat(line, word);
        while (inFile >> word)
        {
            if (strlen(line) + strlen(word) + 1 > width_)
              justify(line);
            else
              strcat(line, " ");
            strcat(line, word);
        }
        justify(line);
    }
  protected:
    int width_;
  private:
    virtual void justify(char *line) = 0;
};

class LeftStrategy: public Strategy
{
  public:
    LeftStrategy(int width): Strategy(width){}
  private:
     /* virtual */void justify(char *line)
    {
        cout << line << endl;
        line[0] = '\0';
    }
};

class RightStrategy: public Strategy
{
  public:
    RightStrategy(int width): Strategy(width){}
  private:
     /* virtual */void justify(char *line)
    {
        char buf[80];
        int offset = width_ - strlen(line);
        memset(buf, ' ', 80);
        strcpy(&(buf[offset]), line);
        cout << buf << endl;
        line[0] = '\0';
    }
};

class CenterStrategy: public Strategy
{
  public:
    CenterStrategy(int width): Strategy(width){}
  private:
     /* virtual */void justify(char *line)
    {
        char buf[80];
        int offset = (width_ - strlen(line)) / 2;
        memset(buf, ' ', 80);
        strcpy(&(buf[offset]), line);
        cout << buf << endl;
        line[0] = '\0';
    }
};

void TestBed::setStrategy(int type, int width)
{
  delete strategy_;
  if (type == Left)
    strategy_ = new LeftStrategy(width);
  else if (type == Right)
    strategy_ = new RightStrategy(width);
  else if (type == Center)
    strategy_ = new CenterStrategy(width);
}

void TestBed::doIt()
{
  strategy_->format();
}

int main()
{
  TestBed test;
  int answer, width;
  cout << "Exit(0) Left(1) Right(2) Center(3): ";
  cin >> answer;
  while (answer)
  {
    cout << "Width: ";
    cin >> width;
    test.setStrategy(answer, width);
    test.doIt();
    cout << "Exit(0) Left(1) Right(2) Center(3): ";
    cin >> answer;
  }
  return 0;
}

The code above accepts input of how to justify the string and the width of the string. It then calls the appropriate class based upon the justification input.4

This code uses a base class as the interface and "subclasses" to implement the individual algorithms.

Dynamic Languages

The use of the Strategy design pattern in dynamic languages requires a variable that has a function as the value. Separate classes are not needed in dynamic languages.3 Each version of the algorithm is implemented as a different object which is then varied by supplying alternative strategy objects to the context.5

PHP

PHP Strategy design pattern example:

<?php

class StrategyContext {
    private $strategy = NULL; 
    //bookList is not instantiated at construct time
    public function __construct($strategy_ind_id) {
        switch ($strategy_ind_id) {
            case "C": 
                $this->strategy = new StrategyCaps();
            break;
            case "E": 
                $this->strategy = new StrategyExclaim();
            break;
            case "S": 
                $this->strategy = new StrategyStars();
            break;
        }
    }
    public function showBookTitle($book) {
      return $this->strategy->showTitle($book);
    }
}

interface StrategyInterface {
    public function showTitle($book_in);
}
 
class StrategyCaps implements StrategyInterface {
    public function showTitle($book_in) {
        $title = $book_in->getTitle();
        $this->titleCount++;
        return strtoupper ($title);
    }
}

class StrategyExclaim implements StrategyInterface {
    public function showTitle($book_in) {
        $title = $book_in->getTitle();
        $this->titleCount++;
        return Str_replace(' ','!',$title);
    }
}

class StrategyStars implements StrategyInterface {
    public function showTitle($book_in) {
        $title = $book_in->getTitle();
        $this->titleCount++;
        return Str_replace(' ','*',$title);
    }
}

class Book {
    private $author;
    private $title;
    function __construct($title_in, $author_in) {
        $this->author = $author_in;
        $this->title  = $title_in;
    }
    function getAuthor() {
        return $this->author;
    }
    function getTitle() {
        return $this->title;
    }
    function getAuthorAndTitle() {
        return $this->getTitle() . ' by ' . $this->getAuthor();
    }
}

  writeln('BEGIN TESTING STRATEGY PATTERN');
  writeln('');

  $book = new Book('PHP for Cats','Larry Truett');
 
  $strategyContextC = new StrategyContext('C');
  $strategyContextE = new StrategyContext('E');
  $strategyContextS = new StrategyContext('S');
 
  writeln('test 1 - show name context C');
  writeln($strategyContextC->showBookTitle($book));
  writeln('');

  writeln('test 2 - show name context E');
  writeln($strategyContextE->showBookTitle($book));
  writeln('');
 
  writeln('test 3 - show name context S');
  writeln($strategyContextS->showBookTitle($book));
  writeln('');

  writeln('END TESTING STRATEGY PATTERN');

  function writeln($line_in) {
    echo $line_in."<br/>";
  }

?>

This code replaces the spaces in a title with another character based upon the input received from the instantiation.

This code uses a context class that will identify the strategy to be used based upon a parameter given at instantiation.4

Ruby

class Context
  def initialize(&strategy)
    @strategy = strategy
  end
 
  def execute
    @strategy.call
  end
end

a = Context.new { puts 'One strategy for the context' }
a.execute
 
b = Context.new { puts 'Another strategy for the context' }
b.execute
 
c = Context.new { puts 'An additional strategy for the context' }
c.execute

As in the PHP example, this code uses a context class that will identify the strategy based upon an input parameter.

What is Ruby's advantage?

Ruby's advantage with the Strategy pattern is two fold:

1. Dynamic languages have a clear and distinct advantage over static languages in general. There are are not as many language limitations, a smaller amount of bookkeeping of objects and classes is needed, and design is not class restricted.3

2. Ruby specific features such as duck typing and code blocks.

With both of these advantages, Ruby has the ability to utilize the Strategy Pattern quickly and cleanly. Ruby has a distinct advantage because of its built in features and because it is dynamically typed.

References

(1) Freeman, Eric & Freeman, Elisabeth. Head First Design Patterns. O'Reilly, 2004.

(2) OBJECTED-ORIENTED DESIGN PATTERN DETECTION USING STATIC AND DYNAMIC ANALYSIS IN JAVA SOFTWARE

(3) Design Patterns in Dynamic Programming

(4) Strategy Design Patterns

(5) Olsen, Russ. Design Patterns in Ruby. Addison-Wesley Professional, 2007.