CSC/ECE 517 Fall 2011/ch4 4h as: Difference between revisions
(53 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
==Introduction== | ==Introduction== | ||
A Design | A design Pattern <ref>[http://en.wikipedia.org/wiki/Design_pattern_(computer_science) Design Patterns] - Wikipedia</ref><ref>[http://www.javacamp.org/designPattern/ JavaCamp]</ref> is commonly used almost all over the Software industry to create highly scalable and efficient software. In this article, we focus primarily on four design patterns: [http://en.wikipedia.org/wiki/Singleton_pattern Singleton], [http://en.wikipedia.org/wiki/Adapter_pattern Adapter], [http://en.wikipedia.org/wiki/Command_pattern Command] and [http://en.wikipedia.org/wiki/Strategy_pattern Strategy]. For purpose of effective explanation as well as to give an alternative viewpoint, we have supplemented all the patterns with code examples in [http://en.wikipedia.org/wiki/Java Java]. | ||
==Software Design Patterns== | ==Software Design Patterns== | ||
In Software, a design pattern is a reusable solution which is a | In Software, a design pattern is a reusable solution which is a template to commonly occurring design problems in software design. Design pattern is never a code - solution to the problem; it is always an explanation or set of rules about how common problems in design can be solved. Using design patterns in development leads to more robust and effective software. | ||
Design Patterns can be subdivided into three major types | Design Patterns can be subdivided into three major types: | ||
*[http://en.wikipedia.org/wiki/Creational_pattern Creational patterns] - determine how objects are created | |||
*[http://en.wikipedia.org/wiki/Structural_pattern Structural patterns] - define how objects are related to each other | |||
*[http://en.wikipedia.org/wiki/Behavioral_pattern Behavioral patterns] - define how objects communicate with each other | |||
The singleton is a creational pattern, the adapter is a structural pattern and command and strategy are behavioral patterns. | |||
==Singleton Pattern== | ==Singleton Pattern== | ||
Line 31: | Line 36: | ||
</pre> | </pre> | ||
This is an example of lazy instantiation where the object is not created until the first time it is required. | This is an example of [http://en.wikipedia.org/wiki/Lazy_instantiation lazy instantiation] where the object is not created until the first time it is required. | ||
Though the above implementation is straight forward, it does not work in a multi threaded environment. If two threads call the getInstance method at the same time, race conditions may result in more than one instance of the class being created, violating the singleton pattern. This problem can be easily solved by making the 'getInstance' method mutually exclusive using locks. In JAVA this is easily achieved by making the 'getInstance' method synchronized. | Though the above implementation is straight forward, it does not work in a multi threaded environment. If two threads call the '''''getInstance''''' method at the same time, [http://en.wikipedia.org/wiki/Race_condition race conditions] may result in more than one instance of the class being created, violating the singleton pattern. This problem can be easily solved by making the 'getInstance' method mutually exclusive using locks. In JAVA this is easily achieved by making the 'getInstance' method [http://download.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html synchronized]. | ||
Alternatively we could replace | Alternatively we could replace | ||
Line 44: | Line 49: | ||
</pre> | </pre> | ||
This is an example of eager instantiation and it has a pitfall of wasting memory space if the Singleton object never ends up being used. However it should also be noted that this version is thread safe because the '''''singletonInstance''''' is created as soon as the Singleton class is loaded by the class loader. | This is an example of eager instantiation and it has a pitfall of wasting memory space if the Singleton object never ends up being used. However it should also be noted that this version is [http://en.wikipedia.org/wiki/Thread_safety thread safe] because the '''''singletonInstance''''' is created as soon as the Singleton class is loaded by the class loader. | ||
The above examples present a very common way of implementing the singleton pattern. This is by means an exhaustive list of possible implementations. There are additional methods of achieving the same result such as | The above examples present a very common way of implementing the singleton pattern. This is by means an exhaustive list of possible implementations. There are additional methods of achieving the same result such as double checked locking <ref>[http://en.wikipedia.org/wiki/Double-checked_locking Double Checked Locking] - Wikipedia </ref> and using "enum" data-type as outlined in the book Effective Java <ref>[http://java.sun.com/docs/books/effective/ Effective Java]- Effective Java 2nd Edition By Joshua Bloch</ref>. | ||
The implementation of the singleton pattern in ruby is trivial as it is provided as a mixin by the library. All one has to do to make a class a singleton is to include the module "Singleton" in the definition of the class. | The implementation of the singleton pattern in ruby is trivial as it is provided as a mixin by the library. All one has to do to make a class a singleton is to include the module "Singleton" in the definition of the class. | ||
Line 52: | Line 57: | ||
==Adapter Pattern== | ==Adapter Pattern== | ||
===Formal Definition=== | ===Formal Definition=== | ||
The adapter pattern, also called wrapper pattern, is used to enable two classes with incompatible interfaces to work together without modifying either class. Adapters are common in real world objects, the most common example being electrical socket adapters which enable electrical appliances from one country to work in another country. | The adapter pattern, also called wrapper pattern, is used to enable two classes with incompatible [http://en.wikipedia.org/wiki/Interface_(object-oriented_programming) interfaces] to work together without modifying either class. Adapters are common in real world objects, the most common example being electrical socket adapters which enable electrical appliances from one country to work in another country <ref>Head First Design Patterns By Elisabeth Freeman, Eric Freeman, Bert Bates, Kathy Sierra</ref>. | ||
===Types=== | ===Types=== | ||
Line 68: | Line 73: | ||
The adapter is realized using a class which implements the target interface and has a reference to the adaptee. The implementation translates functional calls made against the target interface into calls made against the interface implemented by the adaptee. The adapter decouples the client from the vendor interface. If the vendor changes a new adapter can be written to accommodate this change. | The adapter is realized using a class which implements the target interface and has a reference to the adaptee. The implementation translates functional calls made against the target interface into calls made against the interface implemented by the adaptee. The adapter decouples the client from the vendor interface. If the vendor changes a new adapter can be written to accommodate this change. | ||
The following is | The following is a Java version of the Square peg round hole example presented in class. | ||
<pre> | <pre> | ||
Line 145: | Line 150: | ||
</pre> | </pre> | ||
The implementation of the adapter pattern is done using delegation in Ruby. This is very similar to the implementation in Java where the adapter class has a reference to the adaptee and defines the functions expected by the client class. The implementation in ruby is much simpler as a result of its dynamically typed nature. | The implementation of the adapter pattern is done using [http://www.khelll.com/blog/ruby/delegation-in-ruby/ delegation] in Ruby. This is very similar to the implementation in Java where the adapter class has a reference to the adaptee and defines the functions expected by the client class. The implementation in ruby is much simpler as a result of its dynamically typed nature. | ||
==Command Pattern== | ==Command Pattern== | ||
Line 262: | Line 267: | ||
=> 40 | => 40 | ||
</pre> | </pre> | ||
=== Properties of the Command Pattern === | |||
*The Command Pattern successfully decouples the object which invokes the operation from the object which actually performs the operation. | |||
*CommandObjects are like normal first-class objects. They can be easily extended, manipulated and handled like every other object. | |||
*The Command pattern can easily handle an undo operation. By maintaining a history of the commands executed, we can undo the last operations in the order that they were performed. | |||
*The Command pattern lets us create a group of operations to be performed in one call of execute. This functionality is called as MacroOperations or Composite Commands. Such commands consist of multiple actions related to different Receivers which can be performed one after the other on just one invocation. | |||
*Due to the excellent structure of the Command pattern, it is easily extensible and hence it is easy to declare and add new Commands. | |||
==Strategy Pattern== | ==Strategy Pattern== | ||
=== Formal Definition === | === Formal Definition === | ||
'' | '' Strategy pattern is a pattern which encapsulates a defined family of algorithms and thus makes them interchangeable. Thus, Strategy pattern allows the Client to change algorithms according to his will. Execution of the Algorithm will take place through a crystallized and common interface.''<ref>Design Patterns: Elements of Reusable Object-Oriented Software By Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides</ref> <ref>[http://sourcemaking.com/design_patterns/strategy Source Matching] - Strategy Pattern</ref> | ||
Let’s take a real world example to understand this pattern much better. Consider a Program where you have to sort a list of numbers. Note that the list of numbers is the data which is common to all algorithms here. If we were to write a program with one class consisting of all the algorithms as functions like [http://en.wikipedia.org/wiki/Bubble_sort BubbleSort], [http://en.wikipedia.org/wiki/Quick_sort QuickSort], [http://en.wikipedia.org/wiki/Selection_sort SelectionSort] etc., the class would become too hard and huge to handle. The Program would consist of a central if-else OR switch case which would use different algorithms according to the Client input. Now, if we need to add another algorithm into this program, we have to add another function and add another else-if condition OR a switch case which is too tedious and dangerous. If we make one mistake in writing this code, we might end up with a broken program. | Let’s take a real world example to understand this pattern much better. Consider a Program where you have to sort a list of numbers. Note that the list of numbers is the data which is common to all algorithms here. If we were to write a program with one class consisting of all the algorithms as functions like [http://en.wikipedia.org/wiki/Bubble_sort BubbleSort], [http://en.wikipedia.org/wiki/Quick_sort QuickSort], [http://en.wikipedia.org/wiki/Selection_sort SelectionSort] etc., the class would become too hard and huge to handle. The Program would consist of a central if-else OR switch case which would use different algorithms according to the Client input. Now, if we need to add another algorithm into this program, we have to add another function and add another else-if condition OR a switch case which is too tedious and dangerous. If we make one mistake in writing this code, we might end up with a broken program.<ref>[http://blogs.microsoft.co.il/blogs/gilf/archive/2009/11/22/applying-strategy-pattern-instead-of-using-switch-statements.aspx Using Strategy instead of switch] - Microsoft Blog</ref> | ||
Strategy Pattern aims at eliminating this problem by defining classes encapsulating different sorting algorithms and then let the Client/user use a common interface to set and call different algorithms at will. | Strategy Pattern aims at eliminating this problem by defining classes encapsulating different sorting algorithms and then let the Client/user use a common interface to set and call different algorithms at will. | ||
=== Common Applications === | === Common Applications === | ||
The strategy pattern should be used when: | The strategy pattern should be used when<ref>[http://www.oodesign.com/ OODesign] - Startegy Pattern</ref>: | ||
*We have different versions of an algorithm to be used in our program. | *We have different versions of an algorithm to be used in our program. | ||
*We have a class which displays different behavior – or a class which has to be configured to display different behaviors. | *We have a class which displays different behavior – or a class which has to be configured to display different behaviors. | ||
Line 395: | Line 407: | ||
>> Strategy-B called | >> Strategy-B called | ||
</pre> | </pre> | ||
== Conclusion == | |||
Design patterns are an essential part of the design process and should be used to create a robust and effective design for the software. The four patterns discussed in this chapter are unique and are most efficient when applied in the correct situations. Singleton pattern can be used when the design calls for a class which has to be instantiated only once. Adapter pattern can be used when the client software has to work with external components or libraries. Command pattern can be used when a single function or action has to linked to multiple commands at different times. Strategy Pattern can be used when we have a choice of using multiple algorithms which work towards achieving the same goal. | |||
==References== | ==References== | ||
<references/> | |||
Latest revision as of 20:06, 21 October 2011
Introduction
A design Pattern <ref>Design Patterns - Wikipedia</ref><ref>JavaCamp</ref> is commonly used almost all over the Software industry to create highly scalable and efficient software. In this article, we focus primarily on four design patterns: Singleton, Adapter, Command and Strategy. For purpose of effective explanation as well as to give an alternative viewpoint, we have supplemented all the patterns with code examples in Java.
Software Design Patterns
In Software, a design pattern is a reusable solution which is a template to commonly occurring design problems in software design. Design pattern is never a code - solution to the problem; it is always an explanation or set of rules about how common problems in design can be solved. Using design patterns in development leads to more robust and effective software.
Design Patterns can be subdivided into three major types:
- Creational patterns - determine how objects are created
- Structural patterns - define how objects are related to each other
- Behavioral patterns - define how objects communicate with each other
The singleton is a creational pattern, the adapter is a structural pattern and command and strategy are behavioral patterns.
Singleton Pattern
Formal Definition
In software engineering the singleton pattern is a creational pattern which is used to ensure that not more than one object is ever created for a class. The singleton also provides a point of global access.
Common Applications
There may be various reasons that necessitate such a requirement for a class.The class may represent the global state of the system or the class may correspond to a master logger which writes into the log file. Some other places where the singleton pattern is applicable are device drivers, registry settings, etc.
Implementation
There are many ways of implementing the singleton pattern. The most common way is to have a method which creates an instance of the object if it does not already exist. Otherwise the existing reference is returned. To make sure that multiple instances are not created the constructor is made private. Also the object which stores the single instance is made a class variable and is thus not tied to any particular instance of the class.
public class Singleton { private static Singleton singletonInstance; private Singleton() { } public static Singleton getInstance() { if (singletonInstance == null) { singletonInstance = new Singleton(); } return singletonInstance; } }
This is an example of lazy instantiation where the object is not created until the first time it is required. Though the above implementation is straight forward, it does not work in a multi threaded environment. If two threads call the getInstance method at the same time, race conditions may result in more than one instance of the class being created, violating the singleton pattern. This problem can be easily solved by making the 'getInstance' method mutually exclusive using locks. In JAVA this is easily achieved by making the 'getInstance' method synchronized.
Alternatively we could replace
private static Singleton singletonInstance;
with
private static Singleton singletonInstance = new Singleton();
This is an example of eager instantiation and it has a pitfall of wasting memory space if the Singleton object never ends up being used. However it should also be noted that this version is thread safe because the singletonInstance is created as soon as the Singleton class is loaded by the class loader.
The above examples present a very common way of implementing the singleton pattern. This is by means an exhaustive list of possible implementations. There are additional methods of achieving the same result such as double checked locking <ref>Double Checked Locking - Wikipedia </ref> and using "enum" data-type as outlined in the book Effective Java <ref>Effective Java- Effective Java 2nd Edition By Joshua Bloch</ref>.
The implementation of the singleton pattern in ruby is trivial as it is provided as a mixin by the library. All one has to do to make a class a singleton is to include the module "Singleton" in the definition of the class.
Adapter Pattern
Formal Definition
The adapter pattern, also called wrapper pattern, is used to enable two classes with incompatible interfaces to work together without modifying either class. Adapters are common in real world objects, the most common example being electrical socket adapters which enable electrical appliances from one country to work in another country <ref>Head First Design Patterns By Elisabeth Freeman, Eric Freeman, Bert Bates, Kathy Sierra</ref>.
Types
There are two types of adapters
- Object adapters - The adapter described in the example is a object adapter. This uses composition to adapt one class to another.
- Class adapters -This type of adapter requires multiple inheritance and works by subclassing both the interfaces.
Common Applications
The adapter pattern is mostly used to adapt to changing third party vendor libraries. Suppose a system works with an external vendor library and suppose we change vendors and the new vendor library implements a different interface. Now the client class expects a different interface and hence it becomes incompatible with the vendor library. We do not want to rewrite the client code and we cannot change the vendor library. The adapter pattern is ideally suited to solve this problem.
Implementation
Before describing anything let us establish some common terminology here.The vendor class is called adpatee, the class which performs the work of the middleman is called the adapter and the interface implemented by the client is called the target interface.
The adapter is realized using a class which implements the target interface and has a reference to the adaptee. The implementation translates functional calls made against the target interface into calls made against the interface implemented by the adaptee. The adapter decouples the client from the vendor interface. If the vendor changes a new adapter can be written to accommodate this change.
The following is a Java version of the Square peg round hole example presented in class.
public interface RoundObject { public float getRadius(); } //Round hole expects the object It test for in the fits function to implement the //RoundObject Interface public class RoundHole implements RoundObject { private float radius; public RoundHole(float radius){ this.radius = radius; } @Override public float getRadius(){ return radius; } public boolean fits(RoundObject peg){ return peg.getRadius() <= radius; } }
public interface SquareObject { public float getWidth(); } //Square peg implements SquareObject which is incompatible with RoundObject //Hence RoundHole cannot test if the SquarePeg object fits directly public class SquarePeg implements SquareObject{ private float width; public SquarePeg(float width){ this.width = width; } @Override public float getWidth(){ return width; } }
//The adapter adapts the SquarePeg object so that it is compatible with the //RoundObject Interface. The adapter implements RoundObject Interface and also has a reference to the SqurePeg Object. public class SquarePegAdapter implements RoundObject { private SquarePeg squarePeg; public SquarePegAdapter(SquarePeg squarePeg){ this.squarePeg = squarePeg; } public float getRadius(){ return (float) Math.sqrt(Math.pow(squarePeg.getWidth()/2,2)*2); } } //Hence this test works public class AdapterTest { public static void main(String[] args) { RoundObject adapter = new SquarePegAdapter(new SquarePeg(100)); RoundHole hole = new RoundHole(10); if(hole.fits(adapter)){ System.out.println("Square peg fits into round hole"); } else{ System.out.println("Square peg does not fit into round hole"); } } }
The implementation of the adapter pattern is done using delegation in Ruby. This is very similar to the implementation in Java where the adapter class has a reference to the adaptee and defines the functions expected by the client class. The implementation in ruby is much simpler as a result of its dynamically typed nature.
Command Pattern
Formal definition
The Command Pattern encapsulates a request as an object and thereby allows us to parametrize other objects with different requests, queue or log requests and support UN-doable operations.
Command Pattern focuses on one important aim: To ensure that the object calling a method is completely unaware of how the method is called, implemented and handled. In other words, it aims at achieving decoupling of the caller and the function being called.
Let us consider a real world example to better understand this pattern. Suppose we have a magical drop-box which has the note “Drop and it will be done”. We will be really happy to just drop of errands like ‘Do my Homework’, ‘Pick up my Laundry’ and many more! The point to consider here is that we are calling an unknown function by dropping errands – unknown to us in terms of implementation details – but with a common crystallized interface – The Drop Box! We as invokers are not concerned about how our homework is done or how our laundry is picked up as long it is done and picked up. We are just concerned to drop off our requests into the Box and let it do the rest.
This is precisely what Command Pattern achieves. Now the sections below will explain how exactly the pattern goes about achieving this aim.
Common Applications
Command Pattern can be used when:
- We need one action/function which can be represented in many ways, like drop-down menu, buttons and popup menu.
- We need a callback function, i.e., register it somewhere to be called later.
- We need to specify and execute the request at different times.
- We need to undo an action by storing its states for later retrieving.
- We need to decouple the invoker Object from the Receiver Object.
- We need an easily extensible program structure.
Participants
- Command
This is an interface which provides the common function (execute) to the Invoker. Thus, the invoker knows that it can carry out the required action using this execute function. This is the interface where additional operations can be declared so that they are available to the invoker. This is the crystallized interface that was mentioned in the above example.
- Invoker
Invoker holds the Command object and when required calls the execute operation of the Command to fulfill the required request.
- Receiver
Receiver is the enlightened one and knows the actual logic of carrying out the required function/request. The receiver is the one who will receive the request through the execute function invoked by the invoker. Any class can act as a receiver.
- CommandObject
The CommandObject is the one which implements the execute function of the Command interface. The CommandObject or ConcreteCommand binds the execute function and the action of the Receiver to be invoked. Thus, this object is the one who actually calls the required action(s) of the Receiver.
- Client
Client creates the required CommandObject and sets its Receiver. Thus, the Client will decide as to which Command will actually be executed. The point to note here is that different commands can have different CommandObjects.
Implementation
The above diagram gives a gist of how the command pattern works. Here is what happens.
1. The Client creates the CommandObject which contains the execute function from the Command interface. The CommandObject provides a specific implementation to the execute function in such a way that it binds a set of receiver actions to the execute function in this CommandObject. This execute can be used to invoke the encapsulated actions of the receiver at any time.
2. The Client further invokes the set-Command method which passes the CommandObject to the Invoker as an argument thereby saving the CommandObject reference within the Invoker. Thus, the CommandObject is now stored in the Invoker for any further use. This Object can be used by the Invoker to call the actions on the Receiver whenever the Client asks for it.
3. Now, the Client decides to ask the Invoker to execute the command. Note that the command can stay in the Invoker as long as required and as long as it is not replaced by a different command. Thus, it can be kept or discarded at any point of time.
4. The Invoker calls the CommandObject’s execute method. Note that the invoker only knows about the execute method at this point of time and nothing else. This ensures decoupling between the invoker object and the receiver object.
5. Once the invoker calls execute, the execute function in the CommandObject is executed which in turn contains encapsulated methods of the Receiver Object. These methods are called and the operation is completed. Note here that the CommandObject should hold a reference to the Receiver class for it to actually have the ability to call Receiver’s functions. This setting is controlled and done by the Client.
Implementation Example
For the implementation Example, lets take a look at how we can implement the Homework function in the example mentioned at the beginning of the pattern explanation.
public class Homework { public void doHomework(){ System.out.println("Homework is done."); } }
public interface Command { public void execute(); }
public class HomeworkCommand implements Command { Homework homework; public setHomework(Homework homework){ this.homework = homework; } public void execute(){ homework.doHomework(); } }
public class Invoker { Command command; // Command is referenced by the common interface. public void setCommand(Command command){ this.command = command; } public void performAction(){ command.execute(); } }
public class Client { public static void main(String args[]){ Homework homework; Invoker invoker; HomeworkCommand hwCommand = new HomeworkCommand(); hwCommand.setHomework(homework); //Set the Receiver invoker.setCommand(hwCommand); // Set Command to Homework. Any other commands can be used. invoker.performAction(); // Will execute Homework's execute. } }
Command Pattern in Ruby
Command Pattern in Ruby can be accomplished by using Procs. Procs are procedures which consist of binding of variables in its scope when it is created. When we call any Proc, it is not necessary for the caller to know the internal details of the Proc or how it is implemented. The caller just has to pass the required arguments and get the output. This ensures the decoupling of the caller from the method.
Procs make is easy to implement the Command Pattern efficiently in Ruby. A simple Example is shown below.
increment_by_20 = Proc.new { |n| n+20 } increment_by_20.call 20 => 40
Properties of the Command Pattern
- The Command Pattern successfully decouples the object which invokes the operation from the object which actually performs the operation.
- CommandObjects are like normal first-class objects. They can be easily extended, manipulated and handled like every other object.
- The Command pattern can easily handle an undo operation. By maintaining a history of the commands executed, we can undo the last operations in the order that they were performed.
- The Command pattern lets us create a group of operations to be performed in one call of execute. This functionality is called as MacroOperations or Composite Commands. Such commands consist of multiple actions related to different Receivers which can be performed one after the other on just one invocation.
- Due to the excellent structure of the Command pattern, it is easily extensible and hence it is easy to declare and add new Commands.
Strategy Pattern
Formal Definition
Strategy pattern is a pattern which encapsulates a defined family of algorithms and thus makes them interchangeable. Thus, Strategy pattern allows the Client to change algorithms according to his will. Execution of the Algorithm will take place through a crystallized and common interface.<ref>Design Patterns: Elements of Reusable Object-Oriented Software By Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides</ref> <ref>Source Matching - Strategy Pattern</ref>
Let’s take a real world example to understand this pattern much better. Consider a Program where you have to sort a list of numbers. Note that the list of numbers is the data which is common to all algorithms here. If we were to write a program with one class consisting of all the algorithms as functions like BubbleSort, QuickSort, SelectionSort etc., the class would become too hard and huge to handle. The Program would consist of a central if-else OR switch case which would use different algorithms according to the Client input. Now, if we need to add another algorithm into this program, we have to add another function and add another else-if condition OR a switch case which is too tedious and dangerous. If we make one mistake in writing this code, we might end up with a broken program.<ref>Using Strategy instead of switch - Microsoft Blog</ref>
Strategy Pattern aims at eliminating this problem by defining classes encapsulating different sorting algorithms and then let the Client/user use a common interface to set and call different algorithms at will.
Common Applications
The strategy pattern should be used when<ref>OODesign - Startegy Pattern</ref>:
- We have different versions of an algorithm to be used in our program.
- We have a class which displays different behavior – or a class which has to be configured to display different behaviors.
- We have a class which consists of different operations which are inefficiently expressed as multiple if-else statements or switch cases.
- We have an algorithm which is to be implemented in such a say that the user should know nothing about it. Thus, algorithm should be encapsulated from the user.
Participants
- Strategy
Strategy defines a common interface to be used and implemented by the actual Strategy Object – which can also be named as ConcreteStrategy. The function(s) declared in this interface are used by the Context to invoke the actual Strategy.
- ConcreteStrategy
ConcreteStrategy is the enlightened one in this pattern. This class encapsulates the required algorithmic functionality into the function exposed by the Strategy interface. Thus, all the logic to do the work lies in the ConcreteStrategy. By the pattern definition, there can be multiple ConcreteStrategys.
- Context
Context contains the reference to the ConcreteStrategy Object. This reference has to be configured prior to invoking the Strategy which is also handled by the Context. Additionally, it can also accept parameters which are to be passed on to the ConcreteStrategy Object. If the Strategy needs to access data from the Context, it might declare an interface to do so.
Implementation and Working
Let us consider an example of Strategy pattern consisting of two Sorting Algorithms - Bubble Sort and Quick Sort.
public interface SortStrategy { public void sort(int list[]); }
public class ConcreteStrategyBubbleSort implements SortStrategy{ public void sort(int list[]){ //Bubble Sort Logic } }
public class ConcreteStrategyQuickSort implements SortStrategy{ public void sort(int list[]){ //Quick Sort Logic } //Additional Helper Functions }
public class SortContext { private SortStrategy strategy; public void doSort(int list[]){ strategy.sort(list); } public SortStrategy getStrategy() { return strategy; } public void setStrategy(SortStrategy strategy) { this.strategy = strategy; } }
public class Client { public static void main(String args[]){ SortContext context = new SortContext(); ConcreteStrategyBubbleSort bubble = new ConcreteStrategyBubbleSort(); //ConcreteStrategy for Bubble Sort ConcreteStrategyQuickSort quick = new ConcreteStrategyQuickSort(); //ConcreteStrategy for Quick Sort int[] array = {23,99,45,12,0,8,100,49,48}; context.setStrategy(bubble); //Sort with Bubble Sort context.doSort(array); context.setStrategy(quick); //Sort with Quick Sort context.doSort(array); } }
Explanation
- First we have to declare a common interface called Strategy (SortStrategy) which consists of the method that will be used by the ConcreteStrategy Class (i.e. sort (list)).
- This interface is extended by the ConcreteStrategy classes i.e. ConcreteStrategyBubbleSort and ConcreteStrategyQuickSort in which they add their own implementation of the sort function. Note there that since these classes are first-class objects they can avail the use of any number of helper functions as long as they implement the sort function successfully. This is the sort function that will be called when the Client invokes it.
- Thirdly, we have the Context i.e. SortContext which has a reference to the ConcreteStrategy Object within itself. It uses an instance of the common interface to refer to the ConcreteStrategy Object. Context will set the required Strategy in its setter method. It also creates a function i.e. doSort() which binds the ConcreteStrategy sort function to itself. Thus, the client has access to this function to invoke any of the Strategies.
- Finally, we have the Client which has the instances of Context and the ConcreteStrategies. The Client decided which strategy to use and at what time. The Client uses the setter method of the Context to set a particular strategy and then call the operation by using the Context’s `doSort` method. Note here that the Client can replace or modify the Strategies at any point of time. We can also declare new Strategies just by declaring a new class to encapsulate the new Strategy. This saves centralized fat Class consisting of all the Strategies and if-else conditions to use those strategies.
Strategy pattern thus successfully encapsulates different algorithms and makes them easy to use and extend.
Strategy Pattern in Ruby
Proc objects are used in Ruby to implement Strategy pattern effectively. Proc are just objects referenced by symbols (which is the function name itself). These symbols can be passed as objects to any function in Ruby. This enables us to implement strategy pattern. A Proc is normally invoked by using the Proc.call method. This proves to be the common interface which can be used to invoke any Proc at any time.
For example:
def strategy_a Proc.new { puts “Strategy-A Called” } end def strategy_b Proc.new { puts “Strategy-B Called” } end class Context attr_accessor :strategy def setStrategy(func) @strategy = func end def callStrategy @strategy.call end end c = Context.new c.setStrategy strategy_a c.callStrategy >> Strategy-A called c.setStrategy strategy_b c.callStrategy >> Strategy-B called
Conclusion
Design patterns are an essential part of the design process and should be used to create a robust and effective design for the software. The four patterns discussed in this chapter are unique and are most efficient when applied in the correct situations. Singleton pattern can be used when the design calls for a class which has to be instantiated only once. Adapter pattern can be used when the client software has to work with external components or libraries. Command pattern can be used when a single function or action has to linked to multiple commands at different times. Strategy Pattern can be used when we have a choice of using multiple algorithms which work towards achieving the same goal.
References
<references/>