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

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
Line 3: Line 3:
= Strategy Pattern =
= Strategy Pattern =


Strategy pattern is one of the several software design patterns, where in algorithms can be selected on-the-fly at runtime execution of the program. The Strategy pattern lets one build software as a loosely coupled collection of interchangeable parts, in contrast to a monolithic, tightly coupled system. That loose coupling makes the software much more extensible, maintainable, and reusable. The strategy pattern is useful for situations where it is necessary to dynamically swap the algorithms used in an application.  
The Strategy design pattern allows to define a family of algolrithms and makes them interchangeable. The Strategy pattern lets one build software as a loosely coupled collection of interchangeable parts, in contrast to a monolithic, tightly coupled system. That loose coupling makes the software much more extensible, maintainable, and reusable. The strategy pattern is useful for situations where it is necessary to dynamically swap the algorithms used in an application.  


== Concept to explore ==
== Concept to explore ==
Line 72: Line 72:
= Real World Problem =
= Real World Problem =


To demonstrate further let us consider an application that manages songs and playlists, and helps you copy/synchronize songs to you compact MP3 player or Mobile phone.The application accepts a playlist which has a catalog of MP3 files in different directories.  
To demonstrate further let us consider an application that manages songs and playlists, and helps you copy/synchronize songs to you compact MP3 player or Mobile phone. The application accepts a playlist which has a catalog of MP3 files in different directories.  


Let critical actions performed on the files in playlist be renaming, copying or reencoding. Let each file to be copied/synchronized be represented by <code>FileJob</code> object containing the source and destination paths. The actual action (rename/copy/reencode) that needs to be performed is given to the <code>FileJob</code> as an object implementing the <code>FileOperation</code> interface (which is the strategy). The application then has three different file operations: <code>CopyOperation</code>, <code>RenameOperation</code> and <code>ReencodeOperation</code> (which are the implementations). Here the file under the operations takes the role of context.
Let critical actions performed on the files in playlist be renaming, copying or reencoding. Let each file to be copied/synchronized be represented by a <code>FileJob</code> object containing the source and destination paths. The actual action (rename/copy/reencode) that needs to be performed is given to the <code>FileJob</code> as an object implementing the <code>FileOperation</code> interface (which is the strategy). The application then has three different file operations: <code>CopyOperation</code>, <code>RenameOperation</code> and <code>ReencodeOperation</code> (which are the implementations). Here the file under the operations takes the role of context.




Line 86: Line 86:
Here is the Java implementation of the playlist synchronization application.
Here is the Java implementation of the playlist synchronization application.


<pre>
<code>
Agustin.. Please paste your commented JAVA code here...
import java.util.ArrayList;
</pre>
 
// This is the Strategy interface
interface FileOperation {
  public void Run(String from, String to);
}
 
// These are the three different implementations
class CopyOperation implements FileOperation {
  public void Run(String from, String to) {
    System.out.println("Copy " + from + " to " + to + ",");
  }
}
 
class ReencodeOperation implements FileOperation {
  private int bitRate_; // In Kbps
 
  ReencodeOperation(int bitRate) {
    bitRate_ = bitRate;
  }
 
  public void Run(String from, String to) {
    System.out.println("Reencode " + from + " to " + to + ",");
  }
}
 
class RenameOperation implements FileOperation {
  public void Run(String from, String to) {
    System.out.println("Rename " + from + " to " + to + ",");
  }
}
 
// This is the context class
class FileJob {
  String from_;
  String to_;
  FileOperation op_;
 
  public FileJob(String from, String to, FileOperation op) {
    from_ = from;
    to_ = to;
    op_ = op;
  }
 
  void Run() {
    op_.Run(from_, to_);
  }
}
 
// Sample code
class Test {
  public static void main(String[] args) {
    ArrayList<FileJob> jobs = new ArrayList<FileJob>();
 
    FileOperation copyOp = new CopyOperation();
    FileOperation renameOp = new RenameOperation();
    FileOperation reencodeOp = new ReencodeOperation(128); // Kbps
     
    jobs.add(new FileJob("file1", "file2", copyOp));
    jobs.add(new FileJob("file3", "file4", renameOp));
    jobs.add(new FileJob("file5", "file6", reencodeOp));
 
    for (FileJob j : jobs) {
      j.Run();
    }
  }
}
</code>




Line 95: Line 161:
Here is the Ruby implementation of the playlist synchronization application.
Here is the Ruby implementation of the playlist synchronization application.


<pre>
<copy>
Agustin.. Please paste your commented RUBY code here...
# To give the file operation invocation a better name we define an alias, note
</pre>
# that this is optional, one can just use 'call', but it is better to adapt
# the name to the problem domain
class FileOperation < Proc
  alias_method :run, :call
end
 
# These are the strategies
copyOp    = FileOperation.new { |from, to| puts "Copy #{from} to #{to}." }
renameOp  = FileOperation.new { |from, to| puts "Rename #{from} to #{to}." }
bitRate = 128 # Kbps
reencodeOp = FileOperation.new { |from, to| puts "Reencode #{from} to #{to} at #{bitRate}." }
 
# This is the context class
class FileJob
  def initialize(from, to, op)
    @from = from
    @to = to
    @op = op
  end
 
  def run
    @op.run(@from, @to)
  end
end
 
# The same test as the Java implementation
jobs = Array.new
 
jobs << FileJob.new("file1", "file2", copyOp);
jobs << FileJob.new("file3", "file4", renameOp);
jobs << FileJob.new("file5", "file6", reencodeOp);
 
jobs.each { |j| j.run() }
</copy>


= Java versus Ruby =
= Java versus Ruby =
Line 107: Line 206:
! Ruby's Strategy Implementation
! Ruby's Strategy Implementation
|-  
|-  
| This is not there
| More boilerplate code due to the static nature of the language
| This is there
| More succint code
|-
|-
| This is not there
| Parameters to strategies must be instance or class variables
| This is there
| Closures make it trivial to add parameters to the stategies
|-
|-
| This is not there
| This is not there

Revision as of 15:51, 30 September 2007

Note: Santthosh (sbselvad@ncsu.edu) and Agustin (agusvega@nc.rr.com) are editing this page.

Strategy Pattern

The Strategy design pattern allows to define a family of algolrithms and makes them interchangeable. The Strategy pattern lets one build software as a loosely coupled collection of interchangeable parts, in contrast to a monolithic, tightly coupled system. That loose coupling makes the software much more extensible, maintainable, and reusable. The strategy pattern is useful for situations where it is necessary to dynamically swap the algorithms used in an application.

Concept to explore

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?).

Definitions

Design Patterns

A design pattern is a general repeatable solution to a commonly occurring problem in software design. A design pattern is neither a finished design nor a solution that can be transformed directly into code. It is a template on how to solve a problem that can be used in many different situations. Design patterns are more often use in Object oriented designs as they typically show relationships and interactions between classes or objects, without specifying the final application classes or objects that are involved.

Note: Algorithms are not design patterns, algorithms solve computational problems and design patterns solve design problems.

Strategy Pattern

The Strategy Design Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeagle. Stratergy lets the algorithm vary independently from clients that use it.

Merits

  1. A family of algorithms can be defined as a class hierarchy and can be used interchangeably to alter application behavior without changing its architecture.
  2. By encapsulating the algorithm separately, new algorithms complying with the same interface can be easily introduced.
  3. The application can switch strategies at run-time.
  4. Strategy enables the clients to choose the required algorithm, without using a "switch" statement or a series of "if-else" statements.
  5. Data structures used for implementing the algorithm is completely encapsulated in Strategy classes. Therefore, the implementation of an algorithm can be changed without affecting the Context class.
  6. Strategy Pattern can be used instead of sub-classing the Context class. Inheritance hardwires the behavior with the Context and the behavior cannot be changed dynamically.
  7. The same Strategy object can be strategically shared between different Context objects. However, the shared Strategy object should not maintain states across invocations.

Demerits

  1. The application must be aware of all the strategies to select the right one for the right situation.
  2. Strategy and Context classes may be tightly coupled. The Context must supply the relevant data to the Strategy for implementing the algorithm and sometimes, all the data passed by the Context may not be relevant to all the Concrete Strategies.
  3. Context and the Strategy classes normally communicate through the interface specified by the abstract Strategy base class. Strategy base class must expose interface for all the required behaviors, which some concrete Strategy classes might not implement.
  4. In most cases, the application configures the Context with the required Strategy object. Therefore, the application needs to create and maintain two objects in place of one.
  5. Since, the Strategy object is created by the application in most cases; the Context has no control on lifetime of the Strategy object. However, the Context can make a local copy of the Strategy object. But, this increases the memory requirement and has a sure performance impact.

Related Patterns

Command and Bridge are two closely related patterns to Strategy.

  • A Command pattern encapsulates a single action, therefore it tends to have a single method with a rather generic signature. It often is intended to be stored for a longer time and to be executed later. Strategy, in contrast, is used to customize an algorithm. A strategy might have a number of methods specific to the algorithm. Most often strategies will be instantiated immediately before executing the algorithm, and discarded afterwards.
  • Bridge pattern is meant for structure, whereas the Strategy pattern is meant for behavior. The coupling between the context and the strategies is tighter than the coupling between the abstraction and the implementation in the Bridge pattern.

Alternatives

In some programming languages, such as those without polymorphism, the issues addressed by strategy pattern are handled through forms of reflection, such as the native function pointer or function delegate syntax.

Example & Illustration

Normally there are 3 types of classes and or objects that participate in a strategy pattern

  • Strategy
declares an interface common to all supported algorithms. Context uses this interface to call the algorithm defined by a ConcreteStrategy
  • ConcreteStrategy
implements the algorithm using the Strategy interface
  • Context
is configured with a ConcreteStrategy object
maintains a reference to a Strategy object
may define an interface that lets Strategy access its data.


Sort using Strategy


For example lets take the general case of sorting a list. One may wish to use different sort strategies based on the length of the list as performance of these sort algorithms vary depending on the size of the list. Here SortStrategy will be the common interface to all supported sort algorithms (like QuickSort, MergeSort, SyncSort etc.,) and SortedList will be the context that lets Strategy access the list and operate on it.

Real World Problem

To demonstrate further let us consider an application that manages songs and playlists, and helps you copy/synchronize songs to you compact MP3 player or Mobile phone. The application accepts a playlist which has a catalog of MP3 files in different directories.

Let critical actions performed on the files in playlist be renaming, copying or reencoding. Let each file to be copied/synchronized be represented by a FileJob object containing the source and destination paths. The actual action (rename/copy/reencode) that needs to be performed is given to the FileJob as an object implementing the FileOperation interface (which is the strategy). The application then has three different file operations: CopyOperation, RenameOperation and ReencodeOperation (which are the implementations). Here the file under the operations takes the role of context.



Java's Solution

Here is the Java implementation of the playlist synchronization application.

import java.util.ArrayList;

// This is the Strategy interface interface FileOperation {

 public void Run(String from, String to);

}

// These are the three different implementations class CopyOperation implements FileOperation {

 public void Run(String from, String to) {
   System.out.println("Copy " + from + " to " + to + ",");
 } 

}

class ReencodeOperation implements FileOperation {

 private int bitRate_; // In Kbps
 ReencodeOperation(int bitRate) {
   bitRate_ = bitRate;
 }
 public void Run(String from, String to) {
   System.out.println("Reencode " + from + " to " + to + ",");
 } 

}

class RenameOperation implements FileOperation {

 public void Run(String from, String to) {
   System.out.println("Rename " + from + " to " + to + ",");
 } 

}

// This is the context class class FileJob {

 String from_;
 String to_;
 FileOperation op_;
 public FileJob(String from, String to, FileOperation op) {
   from_ = from;
   to_ = to;
   op_ = op;
 }
 void Run() {
   op_.Run(from_, to_);
 }

}

// Sample code class Test {

 public static void main(String[] args) {
   ArrayList<FileJob> jobs = new ArrayList<FileJob>();
   FileOperation copyOp = new CopyOperation();
   FileOperation renameOp = new RenameOperation();
   FileOperation reencodeOp = new ReencodeOperation(128); // Kbps
     
   jobs.add(new FileJob("file1", "file2", copyOp));
   jobs.add(new FileJob("file3", "file4", renameOp));
   jobs.add(new FileJob("file5", "file6", reencodeOp));
   for (FileJob j : jobs) {
     j.Run();
   }
 }

}


Ruby's Solution

Here is the Ruby implementation of the playlist synchronization application.

<copy>

  1. To give the file operation invocation a better name we define an alias, note
  2. that this is optional, one can just use 'call', but it is better to adapt
  3. the name to the problem domain

class FileOperation < Proc

 alias_method :run, :call

end

  1. These are the strategies

copyOp = FileOperation.new { |from, to| puts "Copy #{from} to #{to}." } renameOp = FileOperation.new { |from, to| puts "Rename #{from} to #{to}." } bitRate = 128 # Kbps reencodeOp = FileOperation.new { |from, to| puts "Reencode #{from} to #{to} at #{bitRate}." }

  1. This is the context class

class FileJob

 def initialize(from, to, op)
   @from = from
   @to = to
   @op = op
 end
 def run
   @op.run(@from, @to)
 end

end

  1. The same test as the Java implementation

jobs = Array.new

jobs << FileJob.new("file1", "file2", copyOp); jobs << FileJob.new("file3", "file4", renameOp); jobs << FileJob.new("file5", "file6", reencodeOp);

jobs.each { |j| j.run() } </copy>

Java versus Ruby

Comparison

Let us perform a comparison to better understand the implementation

Java's Strategy Implementation Ruby's Strategy Implementation
More boilerplate code due to the static nature of the language More succint code
Parameters to strategies must be instance or class variables Closures make it trivial to add parameters to the stategies
This is not there This is there
This is not there This is there
This is there This is not there
This is there This is not there
This is there This is not there
This is there This is not there

Where Ruby wins?

  1. Ruby wins 1
  2. Ruby wins 2
  3. Ruby wins 3
  4. Ruby wins 4
  5. Ruby wins 5
  6. Ruby wins 6
  7. Ruby wins 7
  8. Ruby wins 8

References

Books

  1. Programming Ruby: The programmatic programmer’s guide
  2. Head First Design Patterns

Links

  1. Strategy Pattern - Data and Object Factory
  2. Design Patterns - Wikipedia
  3. Strategy for Success - Java World

See Also

  1. Strategy Design Pattern and Open-Closed Principle
  2. Strategy Pattern for PHP Explained