CSC/ECE 517 Fall 2010/ch3 3h ss: Difference between revisions
No edit summary |
No edit summary |
||
(7 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
== The Strategy Pattern == | == The Strategy Pattern == | ||
The Strategy pattern is | The Strategy pattern is one of the most common [http://en.wikipedia.org/wiki/Design_pattern_%28computer_science%29 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.<sup>1</sup> This allows those clients to be [http://en.wikipedia.org/wiki/Coupling_%28computer_science%29 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.<sup>4</sup> | ||
[[Image:diagram.jpg]] | |||
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 [http://en.wikipedia.org/wiki/Type_system 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 == | == Static Languages == | ||
Line 6: | Line 12: | ||
=== Java === | === Java === | ||
[http://sourcemaking.com/design_patterns/strategy/java/1 Java Strategy design pattern example.] | [http://sourcemaking.com/design_patterns/strategy/java/1 Java Strategy design pattern example:] | ||
This code identifies an interface for the related algorithms | |||
<pre> | |||
// 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] ); | |||
} | |||
} | |||
} | |||
</pre> | |||
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.<sup>4</sup> | |||
This code snippet also makes use of the [http://en.wikipedia.org/wiki/Template_method_pattern Template method design pattern]. | |||
=== C++ === | === C++ === | ||
[http://sourcemaking.com/design_patterns/strategy/cpp/1 C++ Strategy design pattern example.] | [http://sourcemaking.com/design_patterns/strategy/cpp/1 C++ Strategy design pattern example:] | ||
<pre> | |||
#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; | |||
} | |||
</pre> | |||
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.<sup>4</sup> | |||
This code uses a base class as the interface and "subclasses" to implement the individual algorithms. | |||
This code uses a | |||
== Dynamic Languages == | == 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.<sup>3</sup> Each version of the algorithm is implemented as a different object which is then varied by supplying alternative strategy objects to the context.<sup>5</sup> | 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.<sup>3</sup> Each version of the algorithm is implemented as a different object which is then varied by supplying alternative strategy objects to the context.<sup>5</sup> | ||
=== PHP === | |||
[http://sourcemaking.com/design_patterns/strategy/php PHP Strategy design pattern example:] | |||
<pre> | |||
<?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/>"; | |||
} | |||
?> | |||
</pre> | |||
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.<sup>4</sup> | |||
=== Ruby === | === Ruby === | ||
<pre> | |||
class Context | class Context | ||
def initialize(&strategy) | def initialize(&strategy) | ||
Line 41: | Line 363: | ||
c = Context.new { puts 'An additional strategy for the context' } | c = Context.new { puts 'An additional strategy for the context' } | ||
c.execute | c.execute | ||
</pre> | |||
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.<sup>3</sup> | |||
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 == | == References == | ||
(1) Freeman, Eric & Freeman, Elisabeth. <i>Head First Design Patterns</i>. O'Reilly, 2004. | |||
(1) < | |||
(2) [http://www.google.com/url?sa=t&source=web&cd=6&ved=0CDMQFjAF&url=http%3A%2F%2Fmb-pde.googlecode.com%2Ffiles%2FMasterThesis.pdf&rct=j&q=strategy%20design%20pattern%20static&ei=4MirTMuIJ8L78Aah8rSFBw&usg=AFQjCNHmD-Y7ojHKraML4IO3khrRrv-Ljw&sig2=AratIpJa6hit5xsOs6MJRQ OBJECTED-ORIENTED DESIGN PATTERN DETECTION USING STATIC AND DYNAMIC ANALYSIS IN JAVA SOFTWARE] | (2) [http://www.google.com/url?sa=t&source=web&cd=6&ved=0CDMQFjAF&url=http%3A%2F%2Fmb-pde.googlecode.com%2Ffiles%2FMasterThesis.pdf&rct=j&q=strategy%20design%20pattern%20static&ei=4MirTMuIJ8L78Aah8rSFBw&usg=AFQjCNHmD-Y7ojHKraML4IO3khrRrv-Ljw&sig2=AratIpJa6hit5xsOs6MJRQ OBJECTED-ORIENTED DESIGN PATTERN DETECTION USING STATIC AND DYNAMIC ANALYSIS IN JAVA SOFTWARE] | ||
Line 55: | Line 385: | ||
(4) [http://sourcemaking.com/design_patterns/strategy Strategy Design Patterns] | (4) [http://sourcemaking.com/design_patterns/strategy Strategy Design Patterns] | ||
(5) < | (5) Olsen, Russ. <i>Design Patterns in Ruby</i>. Addison-Wesley Professional, 2007. |
Latest revision as of 01:42, 15 October 2010
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
(5) Olsen, Russ. Design Patterns in Ruby. Addison-Wesley Professional, 2007.