CSC/ECE 517 Fall 2007/wiki1b 8 ktrk: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
(correction to method_missing)
No edit summary
 
(23 intermediate revisions by 2 users not shown)
Line 1: Line 1:
==The Question==
Take a case of the Strategy pattern and implement it as succinctly as possible in Ruby and Java. Compare the two implementations in terms of clarity and succinctness. The example should be a "real-world" example. While it may be grossly oversimplified for the purpose of illustration, it should not be totally contrived (i.e., should not raise the question, Why would anyone ever want to do that?).
==Introduction==
==Introduction==


===What is the Strategy Pattern?===
===What is the Strategy Pattern?===
The strategy pattern is a proven object oriented design pattern that can be used to solve a common coupling problem often found in software development. Often during the development of compositional classes behaviors and algorithms can become mixed into the class during development without realizing that these behaviors or algorithms may change, become more complex, or additional behaviors may need to be added. These changes could be implemented by extending the class's behavior through inheritance, but over time this could result in an overly complex class hierarchy. The strategy pattern is a design template that can be used to decouple the behavior and algorithms from object being acted upon.
The strategy pattern is a proven object oriented design pattern that can be used to solve a common coupling problem found in software development. Often during the development of a class, behaviors and algorithms (the strategies) can become mixed into the class while implementing without realizing that these behaviors or algorithms may change, become more complex, or additional behaviors may need to be added. These changes could be implemented by extending the class's behavior through [http://www.gotw.ca/publications/mill06.htm inheritance], but over time this could result in an overly complex class hierarchy. The strategy pattern is a design pattern that can be used to [http://courses.csail.mit.edu/6.170/old-www/2002-Fall/lectures/lecture-09.pdf decouple] the behavior and algorithms (the strategy) from the object being acted upon (the context).[[#References|<sup>1</sup>]]
 
===Generic Strategy Class Diagram===
<p>[[Image:Strategy Class Diagram.jpg|Strategy Class Diagram]]</p>
The general structure of the strategy design pattern has a context class which contains a reference to the strategy class’s behavior interface. The context class can instantiate whichever behavior that it needs statically or at runtime. This design provides much weaker coupling between the context and behavior than if different context classes were derived for each type of behavior.[[#References|<sup>8</sup>]]


===When to Use the Strategy Pattern===
===When to Use the Strategy Pattern===
In the seminal book on design patterns by the Gang-of-Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides), situations (also labeled "code smells" by Martin Fowler in [[==References==|Refactoring]]) are described where the strategy design pattern could be used to architect a better solution. These include:
In the seminal book on design patterns by the Gang-of-Four ("[http://hillside.net/patterns/DPBook/DPBook.html Design Patterns] – Elements of Reusable Software” by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides), situations (also labeled "code smells" by Martin Fowler in [http://martinfowler.com/books.html#refactoring Refactoring]) are described where the strategy design pattern could be used to architect a better solution. These include:[[#References|<sup>1</sup>]]
<ol>
#A class that exhibits many similar behaviors or uses different variations of the same algorithm. Instead of using conditional statements within the class to select the behavior or algorithm, use the strategy pattern to take advantage of polymorphism.
<li> A class that exhibits many behaviors</li>
#A class that uses data structures related to a behavior of the class but not the class itself. The strategy pattern can encapsulate the data specific to the behavior.
<li> A class that uses many variations of an algorithm</li>
#Many similar classes that differ only in a type of behavior. These classes can be combined into a single class with the behavior refactored into separate classes so that the behavior can be selected by the instantiated class.
<li> A class that uses data structures related to a behavior but not the class</li>
<li> Many similar classes that differ only in a type of behavior</li>
</ol>


===Advantages of the Strategy Pattern===
===Advantages of the Strategy Pattern===
By decoupling through the use of composition rather than inheritance, several advantages emerge that improve the maintainability and readability of the code.
By decoupling through the use of composition rather than inheritance, several advantages emerge that improve the maintainability and readability of the code.
<ol>
#Allows classes to change behavior at runtime or design time[[#References|<sup>8</sup>]]
<li> Allows classes to change behavior at runtime or design time</li>
#Decreases code duplication among classes using variations of the same behavior[[#References|<sup>5</sup>]]
<li> Decreases code duplication among classes using variations of the same behavior [http://en.wikipedia.org/wiki/Strategy_pattern]</li>
#Behavior is better encapsulated by not being buried in its context[[#References|<sup>8</sup>]]
<li> Behavior is better encapsulated by not being buried in its context [http://www.exciton.cs.rice.edu/JAvaResources/DesignPatterns/StrategyPattern.htm]</li>
#A well known design pattern communicates the intent of the code more readily[[#References|<sup>9</sup>]]
<li> A well known design pattern communicates the intent of the code more readily</li>
</ol>


===Similar Patterns===
===Similar Patterns===
<p>[http://en.wikipedia.org/wiki/State_pattern State], [http://www.exciton.cs.rice.edu/JAvaResources/DesignPatterns/command.htm Command], and [http://en.wikipedia.org/wiki/Bridge_pattern Bridge]</p>
<p>[http://en.wikipedia.org/wiki/State_pattern State], [http://www.exciton.cs.rice.edu/JAvaResources/DesignPatterns/command.htm Command], and [http://en.wikipedia.org/wiki/Bridge_pattern Bridge]</p>
<p>Use the state pattern when the state of the object changes with a change in behavior.</p>
<p>Use the state pattern instead when the state of the object changes with a change in behavior.</p>
<p>Use the bridge pattern when a structural design is needed.</p>
<p>Use the bridge pattern instead when a structural design is needed.</p>
<p>Use the command pattern instead when the invoking object (the context object in the state pattern) does not have knowlege of the recipient object (the behavior in the state pattern). The intermediary command object takes care of dispatching the request to the appropriate object.</p>
For a discussion of the strategy pattern compared to the state and bridge patterns see [http://lists.cs.uiuc.edu/pipermail/gang-of-4-patterns/2003-November/000064.html Strategy Pattern vs. Bridge Pattern].
For a discussion of the strategy pattern compared to the state and bridge patterns see [http://lists.cs.uiuc.edu/pipermail/gang-of-4-patterns/2003-November/000064.html Strategy Pattern vs. Bridge Pattern].
<p>Use the command pattern when the invoking object (the context object in the state pattern) does not have knowlege of the recipient object (the behavior in the state pattern). The intermediary command object takes care of dispatching the request to the appropriate object.</p>


==Strategy Pattern Implementation in Java and Ruby==
==Strategy Pattern Implementation in Java and Ruby==
For our example, we chose to model a video game where there could be different types of Fighters pitted against each other, as you might see in the [http://en.wikipedia.org/wiki/UFC UFC].  In our case, the strategy is the Attack.  Each Fighter has a different type of Attack according to his training.  The Fighter is therefore an "abstract" class.  In statically-typed Java, the class itself is declared as abstract, and in dynamic Ruby it must be extended (or else the Fighter will not know any techniques).  To keep things simple, our fighters are not very smart or well-trained, so they each only get one Attack.  To see our Fighters in action, there is also a Fight class in Java, and a Fight script which can be run in Ruby.
For our example, we chose to model a video game where there could be different types of Fighters pitted against each other, as you might see in the [http://en.wikipedia.org/wiki/UFC UFC].  In our case, the strategy is the Attack.  Each Fighter has a different type of Attack according to his training.  The Fighter is therefore an "abstract" class [[#References|<sup>3</sup>]].  In statically-typed Java, the class itself is declared as abstract, and in dynamic Ruby it must be extended (or else the Fighter will not know any techniques).  To keep things simple, our fighters are not very smart or well-trained, so they each only get one Attack.  In Java the Attack is an interface that must be implemented as concrete classes, and in Ruby, the attack is a Proc object which encapsulates a behavior [[#References|<sup>4</sup>]].  Specific attacks are implemented as modules with which to extend a fighter [[#References|<sup>6</sup>]].  To see our Fighters in action, there is also a Fight class in Java, and a Fight script which can be run in Ruby.


===Class Diagram===
===Class Diagram===
Line 49: Line 51:
       this.attack = attack;
       this.attack = attack;
     }
     }
    // This method sets the fighter's default attack,
    // which will be different for each type of fighter
     protected abstract void setAttack();
     protected abstract void setAttack();
     public void fight() {
     public void fight() {
Line 59: Line 63:
  public class JudoPlayer extends Fighter {
  public class JudoPlayer extends Fighter {
     public void setAttack() {
     public void setAttack() {
      // The JudoPlayer will execute a LegSweep by default
       setAttack(new LegSweep());
       setAttack(new LegSweep());
     }
     }
Line 67: Line 72:
  public class KickBoxer extends Fighter {
  public class KickBoxer extends Fighter {
     public void setAttack() {
     public void setAttack() {
      // The KickBoxer will execute a RoundhouseKick by default
       setAttack(new RoundhouseKick());
       setAttack(new RoundhouseKick());
     }
     }
Line 75: Line 81:
  public class Boxer extends Fighter {
  public class Boxer extends Fighter {
     public void setAttack() {
     public void setAttack() {
      // The Boxer will execute a Jab by default
       setAttack(new Jab());
       setAttack(new Jab());
     }
     }
Line 83: Line 90:
  public class Wrestler extends Fighter {
  public class Wrestler extends Fighter {
     public void setAttack() {
     public void setAttack() {
      // The Wrestler will execute a ShootIn by default
       setAttack(new ShootIn());
       setAttack(new ShootIn());
     }
     }
Line 129: Line 137:
  public class Fight {
  public class Fight {
     public static void main(String[] args) {
     public static void main(String[] args) {
       Fighter[] fighters = { new Boxer(), new Wrestler() };
       // Create two fighters with default attack types
      Boxer b = new Boxer();
      Wrestler w = new Wrestler();
      Fighter[] fighters = { b, w };
      // Fight three rounds
       for (int round = 0; round < 3; round++) {
       for (int round = 0; round < 3; round++) {
           for (int fighter = 0; fighter < fighters.length; fighter++) {
           for (int fighter = 0; fighter < fighters.length; fighter++) {
             fighters[fighter].fight();
             fighters[fighter].fight();
           }
           }
      }
      // Now swap in new behaviors
      b.setAttack(new RoundhouseKick());
      w.setAttack(new LegSweep());
      for (int fighter = 0; fighter < fighters.length; fighter++) {
          fighters[fighter].fight();
       }
       }
     }
     }
Line 142: Line 160:
  class Fighter
  class Fighter
   attr_accessor :attack
   attr_accessor :attack
   def initialize(technique)
   def initialize(&technique)
     @attack = technique
     @attack = technique
   end
   end
   def fight
   def fight
     @attack.execute
     @attack.call
   end
   end
end
MethodMissing.rb
module MissingMethod
   def method_missing(method_name)
   def method_missing(method_name)
     puts "I don't know how to #{method_name}"
     puts "I don't know how to #{method_name}"
Line 159: Line 173:
JudoPlayer.rb:
JudoPlayer.rb:
  require 'Fighter'
  require 'Fighter'
require 'LegSweep'
  class JudoPlayer < Fighter
  class JudoPlayer < Fighter
   def initialize(technique)
  include LegSweep
     super(technique)
   def initialize
    # The Judo player will attack with a leg sweep by default
     super { leg_sweep }
   end
   end
  end
  end
Line 167: Line 184:
KickBoxer.rb:
KickBoxer.rb:
  require 'Fighter'
  require 'Fighter'
require 'RoundhouseKick'
  class KickBoxer < Fighter
  class KickBoxer < Fighter
   def initialize(technique)
  include RoundhouseKick
     super(technique)
   def initialize
    # The kickboxer will attack with a roundhouse kick by default
     super { roundhouse_kick }
   end
   end
  end
  end
Line 175: Line 195:
Boxer.rb:
Boxer.rb:
  require 'Fighter'
  require 'Fighter'
require 'Jab'
  class Boxer < Fighter
  class Boxer < Fighter
   def initialize(technique)
  include Jab
     super(jab)
   def initialize
    # The boxer will attack with a jab by default
     super { jab }
   end
   end
  end
  end
Line 183: Line 206:
Wrestler.rb:
Wrestler.rb:
  require 'Fighter'
  require 'Fighter'
require 'ShootIn'
  class Wrestler < Fighter
  class Wrestler < Fighter
   def initialize(technique)
  include ShootIn
     super(shoot_in)
   def initialize
    # The wrestler will attack with a shoot in by default
     super { shoot_in }
   end
   end
  end
  end


RoundhouseKick.rb:
RoundhouseKick.rb:
  require 'MethodMissing'
  module RoundhouseKick
class RoundhouseKick
   def roundhouse_kick
  include MethodMissing
   def execute
     puts "Roundhouse Kick!"
     puts "Roundhouse Kick!"
   end
   end
Line 199: Line 223:


ShootIn.rb:
ShootIn.rb:
  require 'MethodMissing'
  module ShootIn
class ShootIn
   def shoot_in
  include MethodMissing
   def execute
     puts "Shoot In!"
     puts "Shoot In!"
   end
   end
Line 208: Line 230:


LegSweep.rb:
LegSweep.rb:
  require 'MethodMissing'
  module LegSweep
class LegSweep
   def leg_sweep
  include MethodMissing
   def execute
     puts "Leg Sweep!"
     puts "Leg Sweep!"
   end
   end
Line 217: Line 237:


Jab.rb:
Jab.rb:
  require 'MethodMissing'
  module Jab
class Jab
   def jab
  include MethodMissing
   def execute
     puts "Jab!"
     puts "Jab!"
   end
   end
Line 226: Line 244:


Fight.rb:
Fight.rb:
require 'Boxer'
require 'Wrestler'
require 'KickBoxer'
require 'JudoPlayer'
# Fight three rounds
  rounds = 3
  rounds = 3
  fighters = [ JudoPlayer.new(LegSweep.new), KickBoxer.new(RoundhouseKick.new) ]
  # Create two fighters with default attack types
b = Boxer.new
w = Wrestler.new
fighters = [ b, w ]
  rounds.times do
  rounds.times do
   fighters.each do |fighter|
   fighters.each do |fighter|
Line 233: Line 259:
   end
   end
  end
  end
 
  # Now swap in new behaviors
  # now swap in new behaviors
  b.attack = Proc.new { puts "Front Kick!" }
  fighters[0].attack = ShootIn.new
include LegSweep
  fighters[1].attack = Jab.new
  w.attack = Proc.new { leg_sweep }
 
  fighters.each do |fighter|
  fighters.each do |fighter|
   fighter.fight
   fighter.fight
Line 243: Line 268:


==Comparison of Implementations==
==Comparison of Implementations==
It would be nice to be able to say that one implementation was always clearer or more succinct than the other, but as with most things, it is a compromise.  To get a little more clarity in Java, you sacrifice some succinctness, and in Ruby you can get more succinctness for the price of some clarity.


===Clarity===
===Clarity===
The Java implementation relies less on language idioms, making it somewhat more clear (but not always).  For example, to anyone familiar with O-O languages (but not necessarily Java), it is clear what each of the concrete Fighter types does, as well as the Attacks.  However, the abstract Fighter class might not be as obvious (but probably still should be).  Both languages can do the basic implementation of the framework of the design pattern with near equal clarity.  Choosing a design implementation may come down to choosing the language based on familiarity or design decisions outside the scope of this discussion. Once the framework of the design is in place, there are many features of the Ruby language that could be used to make the implementation more concise than than the Java version.  
Both languages can do the basic implementation of the design pattern framework with near equal clarity.  The Java implementation relies less on language idioms, making it somewhat more clear (but not always).  For example, to anyone familiar with O-O languages (but not necessarily Java), it is clear what each of the concrete Fighter types does, as well as the Attacks.  However, the abstract Fighter class might not be as obvious (but probably still should be).  In Ruby, the strategy is implemented as a Proc object, so code to "call" an attack may not be as self-commenting as to execute an Attack. Also, it is obvious that setAttack(new LegSweep()) sets the attack to be a leg sweep, but the call to super { leg_sweep } may not be.


===Succinctness===
===Succinctness===
In general, the Ruby implementation is more concise.  Ruby's built-in iterators make it easier to work with collections, like arrays of Fighters, or rounds in a Fight.  Also, the Ruby version contains 10 fewer lines of code than the Java implementation (13 fewer if you don't count the method_missing method added to Fighter that is called if you ask him to execute an attack he hasn't learned)However, if you ignore packaging (Java package statements), the difference is much lessConsidering that, since Ruby does not need Interfaces there is one less file in the Ruby implementation, there is hardly any difference at all.   
Overall, the two implementations are fairly similar in their succinctness, but in general, the Ruby implementation is more concise.  Ruby's built-in iterators make it easier to work with collections, like arrays of Fighters, or rounds in a Fight.  Also, the Ruby version contains fewer lines of code than the Java implementation.  If you ignore packaging (Java package statements) though, there are actually more lines in the Ruby implementationHowever, there are features of the Ruby language that could be used to make the implementation more concise than than the Java version.  For example, using its block construct and Proc objects it is possible to eliminate the behavioral classes altogetherTo make a comparison with Java, the Java strategy pattern would need to be implemented using anonymous inner classes, in which case Ruby's more versatile block construct would be easier to implement and maintain.


Overall, the two implementations are fairly similar in their clarity. However, Ruby does offer a means to simplify the code even further by using its block construct or Proc objects. This will work for simple strategy patterns and will allow for the elimination of the behavioral classes altogether. To make a comparison with Java, the Java strategy pattern would need to be implemented using anonymous inner classes, in which case Ruby's more versatile block construct would be easier to implement and maintain. The real advantage to using Ruby becomes more apparent when you extend the design to model more real-world situations.  Then you can take advantage of its dynamic nature.  For example, let's consider when a Fighter wants to learn new techniques, and be able to choose which one to use.  In Java, you need to create new Attack implementations before you can use them in a Fight.  Then you need to create a mechanism to choose which attack to use.  The block idiom in Ruby would allow new attack behaviors to be created on the fly at runtime in addition to being assigned at runtime. This meta-programming capability allows flexibility that Java does not support. Also, if you wanted to combine several attacks into an arsenal from which the fighter could choose any attack, this could be easily accomplished using Ruby's arrays and built-in iterators.
The real advantage to using Ruby becomes more apparent when you extend the design to model more real-world situations.  Then you can take advantage of its dynamic nature.  For example, let's consider when a Fighter wants to learn new techniques, and be able to choose which one to use.  In Java, you need to create new Attack implementations before you can use them in a Fight.  Then you need to create a mechanism to choose which attack to use.  The block idiom in Ruby allows new attack behaviors to be created on the fly at run time in addition to being included as modules at compile time. This meta-programming capability allows flexibility that Java does not support. Also, if you wanted to combine several attacks into an arsenal from which the fighter could choose any attack, this could be easily accomplished using Ruby's arrays and built-in iterators.


===Concluding Remarks===
===Concluding Remarks===
 
It would be nice to be able to say that one implementation was always clearer or more succinct than the other, but as with most things, it is a compromise.  To get a little more clarity in Java, you sacrifice some succinctness, and in Ruby you can get more succinctness for the price of some clarity.  Choosing a design implementation may come down to familiarity with the language or design decisions outside the scope of this discussion.


==References==
==References==


<p>Design Patterns, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Addison-Wesley Pulishing, 1995</p>
#Design Patterns, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Addison-Wesley Pulishing, 1995, pp. 315-323
<p>Refactoring: Improving the Design of Existing Code, Martin Fowler, Addison-Wesley Publishing, 1999</p>
#Refactoring: Improving the Design of Existing Code, Martin Fowler, Addison-Wesley Publishing, 1999, pp. 184-188
<p>Head First Design Patterns, Eric Freeman and Elisabeth Freeman with Kathy Sierra and Bert Bates, O'Reilly Media, 2004</p>
#Head First Design Patterns, Eric Freeman and Elisabeth Freeman with Kathy Sierra and Bert Bates, O'Reilly Media, 2004, p.34
<p>Programming Ruby, Dave Thomas with Chad Fowler and Andy Hunt, The Pragmatic Programmers, 2005</p>
#CSC/ECE517 Lecture Notes (lecture 10), Edward F. Gehringer, 2007, p.4
<p>CSC/ECE517 Lecture Notes (lecture 10), Edward F. Gehringer, 2007</p>
#[http://en.wikipedia.org/wiki/Strategy_pattern Strategy Pattern - Wikipedia]
<p>[http://en.wikipedia.org/wiki/Strategy_pattern Strategy Pattern - Wikipedia]</p>
#[http://www.rubynoob.com/articles/2006/5/16/strategy-design-pattern-in-ruby Rubynoob Strategy Design Pattern in Ruby]
<p>[http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=9&t=003846 Command vs Strategy Pattern? (OO, Patterns, UML and Refactoring forum at JavaRanch)]</p>
#[http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/151744 Re: Head First Design Patterns - Strategy Pattern [was: Java/C# "interface" in Ruby ?]]
<p>[http://www.rubynoob.com/articles/2006/5/16/strategy-design-pattern-in-ruby Rubynoob Strategy Design Pattern in Ruby]</p>
#[http://www.exciton.cs.rice.edu/JAvaResources/DesignPatterns/StrategyPattern.htm The Strategy Design Pattern]
<p>[http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/151744 Re: Head First Design Patterns - Strategy Pattern [was: Java/C# "interface" in Ruby ?]]</p>
#CSC/ECE517 Lecture Notes (lecture 11), Edward F. Gehringer, 2007, p.3

Latest revision as of 01:38, 11 October 2007

The Question

Take a case of the Strategy pattern and implement it as succinctly as possible in Ruby and Java. Compare the two implementations in terms of clarity and succinctness. The example should be a "real-world" example. While it may be grossly oversimplified for the purpose of illustration, it should not be totally contrived (i.e., should not raise the question, Why would anyone ever want to do that?).

Introduction

What is the Strategy Pattern?

The strategy pattern is a proven object oriented design pattern that can be used to solve a common coupling problem found in software development. Often during the development of a class, behaviors and algorithms (the strategies) can become mixed into the class while implementing without realizing that these behaviors or algorithms may change, become more complex, or additional behaviors may need to be added. These changes could be implemented by extending the class's behavior through inheritance, but over time this could result in an overly complex class hierarchy. The strategy pattern is a design pattern that can be used to decouple the behavior and algorithms (the strategy) from the object being acted upon (the context).1

Generic Strategy Class Diagram

Strategy Class Diagram

The general structure of the strategy design pattern has a context class which contains a reference to the strategy class’s behavior interface. The context class can instantiate whichever behavior that it needs statically or at runtime. This design provides much weaker coupling between the context and behavior than if different context classes were derived for each type of behavior.8

When to Use the Strategy Pattern

In the seminal book on design patterns by the Gang-of-Four ("Design Patterns – Elements of Reusable Software” by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides), situations (also labeled "code smells" by Martin Fowler in Refactoring) are described where the strategy design pattern could be used to architect a better solution. These include:1

  1. A class that exhibits many similar behaviors or uses different variations of the same algorithm. Instead of using conditional statements within the class to select the behavior or algorithm, use the strategy pattern to take advantage of polymorphism.
  2. A class that uses data structures related to a behavior of the class but not the class itself. The strategy pattern can encapsulate the data specific to the behavior.
  3. Many similar classes that differ only in a type of behavior. These classes can be combined into a single class with the behavior refactored into separate classes so that the behavior can be selected by the instantiated class.

Advantages of the Strategy Pattern

By decoupling through the use of composition rather than inheritance, several advantages emerge that improve the maintainability and readability of the code.

  1. Allows classes to change behavior at runtime or design time8
  2. Decreases code duplication among classes using variations of the same behavior5
  3. Behavior is better encapsulated by not being buried in its context8
  4. A well known design pattern communicates the intent of the code more readily9

Similar Patterns

State, Command, and Bridge

Use the state pattern instead when the state of the object changes with a change in behavior.

Use the bridge pattern instead when a structural design is needed.

Use the command pattern instead when the invoking object (the context object in the state pattern) does not have knowlege of the recipient object (the behavior in the state pattern). The intermediary command object takes care of dispatching the request to the appropriate object.

For a discussion of the strategy pattern compared to the state and bridge patterns see Strategy Pattern vs. Bridge Pattern.

Strategy Pattern Implementation in Java and Ruby

For our example, we chose to model a video game where there could be different types of Fighters pitted against each other, as you might see in the UFC. In our case, the strategy is the Attack. Each Fighter has a different type of Attack according to his training. The Fighter is therefore an "abstract" class 3. In statically-typed Java, the class itself is declared as abstract, and in dynamic Ruby it must be extended (or else the Fighter will not know any techniques). To keep things simple, our fighters are not very smart or well-trained, so they each only get one Attack. In Java the Attack is an interface that must be implemented as concrete classes, and in Ruby, the attack is a Proc object which encapsulates a behavior 4. Specific attacks are implemented as modules with which to extend a fighter 6. To see our Fighters in action, there is also a Fight class in Java, and a Fight script which can be run in Ruby.

Class Diagram

Strategy pattern class diagram

Java

Fighter.java:

package csc517.wiki1b;
public abstract class Fighter {
   private Attack attack;
   public Fighter(){
      setAttack();
   }
   public Attack getAttack() {
      return attack;
   }
   public void setAttack(Attack attack) {
      this.attack = attack;
   }
   // This method sets the fighter's default attack,
   // which will be different for each type of fighter
   protected abstract void setAttack();
   public void fight() {
      getAttack().execute();
   }
}

JudoPlayer.java:

package csc517.wiki1b;
public class JudoPlayer extends Fighter {
   public void setAttack() {
      // The JudoPlayer will execute a LegSweep by default
      setAttack(new LegSweep());
   }
}

KickBoxer.java:

package csc517.wiki1b;
public class KickBoxer extends Fighter {
   public void setAttack() {
      // The KickBoxer will execute a RoundhouseKick by default
      setAttack(new RoundhouseKick());
   }
}

Boxer.java:

package csc517.wiki1b;
public class Boxer extends Fighter {
   public void setAttack() {
      // The Boxer will execute a Jab by default
      setAttack(new Jab());
   }
}

Wrestler.java:

package csc517.wiki1b;
public class Wrestler extends Fighter {
   public void setAttack() {
      // The Wrestler will execute a ShootIn by default
      setAttack(new ShootIn());
   }
}

Attack.java:

package csc517.wiki1b;
public interface Attack {
   public void execute();
}

RoundhouseKick.java:

package csc517.wiki1b;
public class RoundhouseKick implements Attack {
   public void execute() {
      System.out.println("Roundhouse Kick!");
   }
}

ShootIn.java:

package csc517.wiki1b;
public class ShootIn implements Attack {
   public void execute() {
      System.out.println("Shoot In!");
   }
}

LegSweep.java:

package csc517.wiki1b;
public class LegSweep implements Attack {
   public void execute() {
      System.out.println("Leg Sweep!");
   }
}

Jab.java:

package csc517.wiki1b;
public class Jab implements Attack {
   public void execute() {
      System.out.println("Jab!");
   }
}

Fight.java:

package csc517.wiki1b;
public class Fight {
   public static void main(String[] args) {
      // Create two fighters with default attack types
      Boxer b = new Boxer();
      Wrestler w = new Wrestler();
      Fighter[] fighters = { b, w };
      // Fight three rounds
      for (int round = 0; round < 3; round++) {
         for (int fighter = 0; fighter < fighters.length; fighter++) {
            fighters[fighter].fight();
         }
      }
      // Now swap in new behaviors
      b.setAttack(new RoundhouseKick());
      w.setAttack(new LegSweep());
      for (int fighter = 0; fighter < fighters.length; fighter++) {
         fighters[fighter].fight();
      }
   }
}

Ruby

Fighter.rb:

class Fighter
  attr_accessor :attack
  def initialize(&technique)
    @attack = technique
  end
  def fight
    @attack.call
  end
  def method_missing(method_name)
    puts "I don't know how to #{method_name}"
  end
end

JudoPlayer.rb:

require 'Fighter'
require 'LegSweep'
class JudoPlayer < Fighter
  include LegSweep
  def initialize
    # The Judo player will attack with a leg sweep by default
    super { leg_sweep }
  end
end

KickBoxer.rb:

require 'Fighter'
require 'RoundhouseKick'
class KickBoxer < Fighter
  include RoundhouseKick
  def initialize
    # The kickboxer will attack with a roundhouse kick by default
    super { roundhouse_kick }
  end
end

Boxer.rb:

require 'Fighter'
require 'Jab'
class Boxer < Fighter
  include Jab
  def initialize
    # The boxer will attack with a jab by default
    super { jab }
  end
end

Wrestler.rb:

require 'Fighter'
require 'ShootIn'
class Wrestler < Fighter
  include ShootIn
  def initialize
    # The wrestler will attack with a shoot in by default
    super { shoot_in }
  end
end

RoundhouseKick.rb:

module RoundhouseKick
  def roundhouse_kick
    puts "Roundhouse Kick!"
  end
end

ShootIn.rb:

module ShootIn
  def shoot_in
    puts "Shoot In!"
  end
end

LegSweep.rb:

module LegSweep
  def leg_sweep
    puts "Leg Sweep!"
  end
end

Jab.rb:

module Jab
  def jab
    puts "Jab!"
  end
end

Fight.rb:

require 'Boxer'
require 'Wrestler'
require 'KickBoxer'
require 'JudoPlayer'
# Fight three rounds
rounds = 3
# Create two fighters with default attack types
b = Boxer.new
w = Wrestler.new
fighters = [ b, w ]
rounds.times do
  fighters.each do |fighter|
    fighter.fight
  end
end
# Now swap in new behaviors
b.attack = Proc.new { puts "Front Kick!" }
include LegSweep
w.attack = Proc.new { leg_sweep }
fighters.each do |fighter|
  fighter.fight
end

Comparison of Implementations

Clarity

Both languages can do the basic implementation of the design pattern framework with near equal clarity. The Java implementation relies less on language idioms, making it somewhat more clear (but not always). For example, to anyone familiar with O-O languages (but not necessarily Java), it is clear what each of the concrete Fighter types does, as well as the Attacks. However, the abstract Fighter class might not be as obvious (but probably still should be). In Ruby, the strategy is implemented as a Proc object, so code to "call" an attack may not be as self-commenting as to execute an Attack. Also, it is obvious that setAttack(new LegSweep()) sets the attack to be a leg sweep, but the call to super { leg_sweep } may not be.

Succinctness

Overall, the two implementations are fairly similar in their succinctness, but in general, the Ruby implementation is more concise. Ruby's built-in iterators make it easier to work with collections, like arrays of Fighters, or rounds in a Fight. Also, the Ruby version contains fewer lines of code than the Java implementation. If you ignore packaging (Java package statements) though, there are actually more lines in the Ruby implementation. However, there are features of the Ruby language that could be used to make the implementation more concise than than the Java version. For example, using its block construct and Proc objects it is possible to eliminate the behavioral classes altogether. To make a comparison with Java, the Java strategy pattern would need to be implemented using anonymous inner classes, in which case Ruby's more versatile block construct would be easier to implement and maintain.

The real advantage to using Ruby becomes more apparent when you extend the design to model more real-world situations. Then you can take advantage of its dynamic nature. For example, let's consider when a Fighter wants to learn new techniques, and be able to choose which one to use. In Java, you need to create new Attack implementations before you can use them in a Fight. Then you need to create a mechanism to choose which attack to use. The block idiom in Ruby allows new attack behaviors to be created on the fly at run time in addition to being included as modules at compile time. This meta-programming capability allows flexibility that Java does not support. Also, if you wanted to combine several attacks into an arsenal from which the fighter could choose any attack, this could be easily accomplished using Ruby's arrays and built-in iterators.

Concluding Remarks

It would be nice to be able to say that one implementation was always clearer or more succinct than the other, but as with most things, it is a compromise. To get a little more clarity in Java, you sacrifice some succinctness, and in Ruby you can get more succinctness for the price of some clarity. Choosing a design implementation may come down to familiarity with the language or design decisions outside the scope of this discussion.

References

  1. Design Patterns, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Addison-Wesley Pulishing, 1995, pp. 315-323
  2. Refactoring: Improving the Design of Existing Code, Martin Fowler, Addison-Wesley Publishing, 1999, pp. 184-188
  3. Head First Design Patterns, Eric Freeman and Elisabeth Freeman with Kathy Sierra and Bert Bates, O'Reilly Media, 2004, p.34
  4. CSC/ECE517 Lecture Notes (lecture 10), Edward F. Gehringer, 2007, p.4
  5. Strategy Pattern - Wikipedia
  6. Rubynoob Strategy Design Pattern in Ruby
  7. Re: Head First Design Patterns - Strategy Pattern [was: Java/C# "interface" in Ruby ?]
  8. The Strategy Design Pattern
  9. CSC/ECE517 Lecture Notes (lecture 11), Edward F. Gehringer, 2007, p.3