CSC/ECE 517 Fall 2012/ch1b 1w42 js: Difference between revisions
Line 268: | Line 268: | ||
attr_reader :title, :text | attr_reader :title, :text | ||
attr_accessor :formatter | attr_accessor :formatter | ||
def initialize(formatter) | def initialize(formatter) | ||
#Initialize title and text | #Initialize title and text | ||
Line 274: | Line 273: | ||
end | end | ||
end | end | ||
class Formatter | class Formatter | ||
def output_report( title, text ) | def output_report( title, text ) | ||
Line 280: | Line 279: | ||
end | end | ||
end | end | ||
class HTMLFormatter < Formatter | class HTMLFormatter < Formatter | ||
def output_report( title, text ) | def output_report( title, text ) | ||
Line 287: | Line 286: | ||
puts('</html>') | puts('</html>') | ||
end | end | ||
end | end | ||
class PlainTextFormatter < Formatter | class PlainTextFormatter < Formatter | ||
def output_report(title, text) | def output_report(title, text) | ||
Line 294: | Line 293: | ||
puts(line) | puts(line) | ||
end | end | ||
end | |||
The above snippet depicts the structure of the Report class, Formatter class(super class), HtmlFormatter class and PlainTextFormatter class(subclasses of Formatter class). The Report class' initialize method invokes the particular formatter class depending upon the formatter specified. These formatter invocations are not specified in advance, hence the Report class has to choose the required formatter function at the run time. | The above snippet depicts the structure of the Report class, Formatter class(super class), HtmlFormatter class and PlainTextFormatter class(subclasses of Formatter class). The Report class' initialize method invokes the particular formatter class depending upon the formatter specified. These formatter invocations are not specified in advance, hence the Report class has to choose the required formatter function at the run time. |
Revision as of 01:20, 4 October 2012
Design Patterns in Ruby
"Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice" - Christopher Alexander
"In software engineering, a design pattern is a general reusable solution to a commonly occurring problem within a given context in software design".
A Design Pattern is a template to solve a problem which can be used in many different situations. It names, abstracts and identifies the key aspects of a common design structure that makes it useful for creating a reusable object - oriented design. Design Patterns in Object - Oriented Languages help show the relationship between the different classes and objects in the program. Design Patterns are tools for building software.
Overview
A design pattern is a general reusable solution to a commonly occurring problem within a given context in software design
The different types of design patterns can be categorized and listed as below:
Creational Pattern, which help create the objects for the user, instead of having the user to instantiate the object.
- Factory Pattern, which allows a class to defer instantiation to subclasses.
- Abstract Factory Pattern, which provides an interface for creating related or dependent objects without specifying the objects' concrete classes.
- Builder Pattern, which separates the construction of a complex object from its representation so that the same construction process can create different representation.
- Prototype Pattern, which specifies the kind of object to create using a prototypical instance, and creates new objects by cloning this prototype.
- Singleton Pattern, which ensures that only one instance of a class is created and provides a global access point to the object.
Structural Pattern, which employ interfaces to achieve inheritance to enable objects to obtain new functionality.
- Adapter Pattern, which 'adapts' one interface for a class into one that a client expects.
- Bridge Pattern, which decouples an abstraction from its implementation so that the two can vary independently.
- Composite Pattern, which represents a tree structure of objects where every object has the same interface.
- Decorator Pattern, which adds additional functionality to a class at runtime where subclassing would result in an exponential rise of new classes
- Facade Pattern, which creates a simplified interface of an existing interface to ease usage for common tasks.
- Flyweight Pattern, where a high quantity of objects share a common properties object to save space.
- Proxy Pattern, where a class functions as an interface to another thing.
Behavioral Pattern, which are concerned with communication between objects.
- Command Pattern, which enables to pass around the code that needs to be executed later.
- Chain of Responsibility Pattern, where command objects are handled or passed on to other objects by logic-containing processing objects.
- Interpreter Pattern, which implements a specialized computer language to rapidly solve a specific set of problems.
- Iterator Pattern, where iterators are used to access the elements of an aggregate object sequentially without exposing its underlying representation.
- Mediator Pattern, which provides a unified interface to a set of interfaces in a subsystem.
- Momento Pattern, which provides the ability to restore an object to its previous state (rollback).
- Observer Pattern, is similar to Publish/Subscribe or Event Listener. Objects register to observe an event that may be raised by another object.
- State Pattern, is a clean way for an object to partially change its type at runtime.
- Strategy Pattern, where Algorithms can be selected on the fly.
- Template Pattern, which describes the program skeleton of a program.
- Visitor Pattern, is a way to separate an algorithm from an object.
Some of the Patterns that are more commonly used with Ruby are explained below.
Creational Patterns
Singleton Pattern
The Singleton pattern is one of the simplest design patterns: it involves only one class which is responsible to instantiate itself, to make sure it creates not more than one instance; in the same time it provides a global point of access to that instance.For example, in a system there should be only one window manager (or only a file system or only a print spooler). Usually singletons are used for centralized management of internal or external resources and they provide a global point of access to themselves. Illustrated below is the syntax of using singleton in Ruby:
require 'singleton' class Registry include Singleton attr_accessor :val end
The Singleton pattern is available as a mixin in the Ruby library. Including it in the code makes the new method private and provides an instance method used to create or access the single instance.
r = Registry.new #throws a NoMethodError as the "new" method is private r = Registry.instance r.val = 5 s = Registry.instance # a new reference to the existing object is created. puts s.val >> 5 s.val = 6 puts r.val >> 6 s.dup >> TypeError: can’t duplicate instance of singleton Registry
Singletons can also be created without using the 'singleton' module.
class Single_ton def initialize puts "Initialized" end @@instance = Single_ton.new def self.instance return @@instance end def print_something puts "This prints something" end private_class_method :new end
The above snippet is actually a singleton class. A cursory reading shall read the logic behind singleton-ing the class. An object to the same class has been created as a class variable. a "instance" method is defined. The "new" method is made 'private'. This makes sure that objects of the Single_ton cannot be created.
puts Single_ton.instance >> <Single_ton:0x94483d4> Single_ton.instance.print_something >> This prints something
The above snippet shows that the class 'behaves' like a singleton.
The class diagram<ref>Singleton pattern - impelmentation. In Oodesign. Retrieved from http://www.oodesign.com/singleton-pattern.html</ref> for Singleton pattern is illustrated below:
The Singleton Pattern defines a getInstance() operation which exposes the unique instance which is accessed by the clients. The getInstance() is is responsible for creating its class unique instance in case it is not created yet and to return that instance. For more details, follow this link
Structural Patterns
Adapter Pattern
An Adapter pattern, also known as the Wrapper Pattern, enables classes with incompatible interfaces to work together, by providing the users with a compatible interface.
In other words, when we want our new class to support a pre-existing feature, instead of modifying our new class, we can 'wrap' a similar feature in our new class to behave like the older class so that the pre-existing feature is supported.
Lets look at a simple example.<ref>Olsen, R. (2007). Filling in the gaps with the adapter. In Design patterns in ruby (pp. 164-165). Addison Wesley.</ref>
Consider a class Encrypter which is used to encrypt files. The encrypt method takes two open files as arguments. One is a reader which is the input file and other is the writer which is the output file where the encrypted data is stored. It uses a simple encryption algorithm to encrypt each character with a user-specified key.
class Encrypter def initialize(key) @key = key end def encrypt(reader, writer) key_index = 0 while not reader.eof? clear_char = reader.getc encrypted_char = clear_char ^ @key[key_index] writer.putc(encrypted_char) key_index = (key_index + 1) % @key.size end end end
Now suppose a string is to be encrypted instead of a file. To be able to use the same Encrypter class an object is required that looks like an open file - exposes the same methods as the Ruby IO object - on the outside, but actually fetches the data from a string on the inside.
So, lets define a StringIOAdapter class:
class StringIOAdapter def initialize(string) @string = string @position = 0 end def getc if @position >= @string.length raise EOFError end ch = @string[@position] @position += 1 return ch end def eof? return @position >= @string.length end end
The StringIOAdapter has two instance variables: a reference to the string to be encrypted and a position index. Each time the function getc is called, the StringIOAdapter will return the character at current position and increment the position index. If no more characters are left in the string an EOFError will be raised. The function eof? will return true if the string has run out of characters but otherwise will return false.
To use Encrypter with StringIOAdapter, the reader input object is defined to be of type StringIOAdapter.
encrypter = Encrypter.new('OOLS') reader= StringIOAdapter.new('TA position open with Prof. XYZ') writer=File.open('out.txt', 'w') encrypter.encrypt(reader, writer)
Thus the Adapter bridges the chasm between the interface you have and the interface you need.
The class diagram of Adapter class is usually given as:
The client expects the target to have some interface. But the target is actually an implementation of the Adapter. The Adapter defines a compatible interface while at the same time there is a reference to a second object - the Adaptee - buried inside it. The Adaptee actually performs the work.
Behavioral Patterns
Command Pattern
The design patterns we saw above deal with classes and work around the way classes and objects are manipulated, interpreted, created etc. Actually, the adapter pattern bases its necessity on the need for 'code reuse'. But, it can only implement code reuse at a class or method level. But sometimes code must be reused at an even finer level. For example, Sometimes one might need to store bunch of code to be used by a peer or at a later stage. This bunch of code is not associated with any class, or any method.
Command Pattern is useful in such cases. With command pattern it is possible to store a piece of code or a set of commands to be executed at a later stage.
Lets consider a simple example of a GUI button that can be configured to do a variety of actions.<ref>Olsen, R. (2007). Getting things done with commands. In Design patterns in ruby (pp. 147-148). Addison Wesley.</ref> These actions could be saving data to database, making a network connection, navigating to another page, etc.
class UserButton attr_accessor :command def initialize() end # # Button rendering and management code # def on_button_push # # Do some action # end end
Without the use of the Command Pattern, we could implement a solution here by inheriting the UserButton into subclasses like DatabaseButton, NetworkButton, etc. For example:
class DatabaseButton attr_accessor :command def initialize() end # # Button rendering and management code # def on_button_push # # Database operations like InsertIntoDatabase(column, value) # end end
This is however a tedious and rigid solution. If suppose on certain button clicks you only wanted to update the database value and not insert. You would either have to create a new subclass with this functionality or check for which operation is required in the on_button_push method and execute separate queries.
The Command Pattern provides an easier way to implement this. The simplest way to implement the Command Pattern in Ruby is through the use of Proc Object with Closures and Blocks.
Consider the following definition for the UserButton class.
class UserButton attr_accessor :command def initialize(&block) @command = block end # # Button rendering and management code # def on_button_push @command.call if @command end end
Here the @command instance variable is a Proc object. In simple terms, it is a chunk of code that can be executed with the call method.
To instantiate a UserButton for database insertion, a code block is passed when the object is created.
new_button = UserButton.new do # # Database operations like InsertIntoDatabase(column, value) # or # Network link creation # or # Navigate to another page # end
The class diagram for the Command Pattern is fairly simple. It consists of a number of classes which share the same interface.
An interesting usage of the command pattern is to implement the Undo functionality.<ref>Olsen, R. (2007). Getting things done with commands. In Design patterns in ruby (pp. 151-153). Addison Wesley. </ref> This requires definition of a unexecute method which undoes the actions done in the execute method. A code example can be found here..
Strategy Pattern
The Strategy Pattern helps choose an algorithm to accomplish a task based on some "parameters" of the situation. Also known as the Policy Pattern, it enables selection of algorithms at runtime. This pattern allows the algorithm to vary irresepctive of the user that uses it.
The strategy pattern "defines a family of algorithms, encapsulates each one, and makes them interchangeable".
Consider as an example, a class that that converts among different types of file formats like jpeg, jif, png etc. A case statement can be used to choose what algorithm has to be employed for each type of format. Another example could be performing validation on incoming data, where selection of a validation algorithm is based on the type and source of data.
A Strategy Pattern is best implemented using Proc Objects. An example<ref>Olsen, R. (2007). 4. replacing the algorithm with the strategy. In Design patterns in ruby (pp. 78-109). Addison Wesley. </ref> of the Strategy Pattern which deals with formatting styles of the report:
class Report attr_reader :title, :text attr_accessor :formatter def initialize(formatter) #Initialize title and text @formatter = formatter end end class Formatter def output_report( title, text ) raise 'Abstract method called' end end class HTMLFormatter < Formatter def output_report( title, text ) puts('<html>') #More HTML formatting code puts('</html>') end end class PlainTextFormatter < Formatter def output_report(title, text) #More Plain Test Fromatting code puts(line) end end
The above snippet depicts the structure of the Report class, Formatter class(super class), HtmlFormatter class and PlainTextFormatter class(subclasses of Formatter class). The Report class' initialize method invokes the particular formatter class depending upon the formatter specified. These formatter invocations are not specified in advance, hence the Report class has to choose the required formatter function at the run time.
report = Report.new(HTMLFormatter.new) #HTML Formatter report.output_report #HTML Formatter method report.formatter = PlainTextFormatter.new #formatter type changed to Plain Text Formatter report.output_report #Plain Text Formatter method
The Strategy pattern is based on composition and delegation, rather than on inheritance, hence it is easy to switch strategies at runtime. Thus the path or strategy chosen is based on the formatter invocation on runtime.Therefore, the algorithm that will be selected for displaying the text, is decided at run time.
Illustrated below is the class diagram of Strategy Pattern Design:
Differences between Command and Strategy Pattern
- A Command Pattern encapsulates a single action. A command object has a single method with a generic signature associated with it.
- A Strategy Pattern, enables us to customize an algorithm, deciding which algorithm has to be made use of depending on a number of dynamically changing factors. A given strategy has many methods associated with it.
Comparsion of the different Design Patterns
Comparison Factor | Singleton Pattern | Adapter Pattern | Command Pattern | Strategy Pattern |
---|---|---|---|---|
Intent | Ensure only one object of a class is instantiated, provide a global point of access to that object | Convert the interface of a class into one that the client expects | Encapsulate a request in an object and allow the parameterization of clients with different requests | Encapsulate a set of algorithms and use them interchangeably. |
Advantages | Helps achieve serialization and is useful in scenarios of logging, communication and lazy instantiations | Enables classes to communicate which otherwise would not be able to due to incompatible interfaces. | Addition of new functionality is fairly simple as it just calls for encapsulating the functionality into the Command Object. | Large conditional statements are eliminated which makes it easy to keep track of the different behaviors which are now in separate classes. |
Disadvantages | Brings in the concept of global state, making unit testing difficult. Also reduces the scope of parallelism within the program. | When using Object Adapters, all the code for delegating all the necessary requests to the Adaptee has to be written. | The increase in the number of Command Classes, clutters up the design. | The increase in the number of objects, and all the algorithms use the same interface. |
We could also add a section on drawbacks of design patterns. I found a link on that.
See Also
References
<references />