CSC/ECE 517 Fall 2011/ch4 4h sv

From Expertiza_Wiki
Jump to navigation Jump to search

Design Patterns in Ruby

Introduction

"In software engineering, a design pattern is a general reusable solution to a commonly occurring problem within a given context in software design".

Design Patterns are tools for building software. A Design Pattern is a template to solve a problem which can be used in many different situations. Design Patterns in Object - Oriented Languages help show the relationship between the different classes and objects in the program.

There are different types of design patterns:

  • Singleton Design Pattern, which is used to have only one instance of a class.
  • Adapter Design Pattern, which enables classes with incompatible interfaces to work together.
  • Command Design Pattern, which enables to pass around the code that needs to be executed later.
  • Algorithm Strategy Pattern, which helps choose an algorithm to fulfill a task based on some "parameter" of the situation.
  • Computational Design Patterns, which addresses the concerns related to key computation identification.
  • Execution Design Patterns, which addresses the concerns related to supporting application execution, including strategies in executing streams of tasks and building blocks to support task synchronization.
  • Implementation Strategy Patterns, which addresses the concerns related to implementing source code to support program organization, and the common data structures specific to parallel programming.
  • Structural Design Patterns, which addresses concerns related to high-level structures of applications being developed.

Singleton Pattern

The Singleton design pattern is used to restrict the instantiation of a class to only one instance which is globally available. This is used in situations where a user needs an instance of the class to be available in various parts of the application, being available for logging functionality, communictaion with external systems and database access etc. 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.

Below is an illustration of the implementation of Singleton Design Pattern in Ruby:

require 'singleton'
class Example
    attr_accessor :val
    include Singleton
end

In the above declaration of a class, including the Singleton module makes the class's new method private. To create an object of that class, the users call the instance method, which returns a singleton instance of the class.

a = Example.instance
b = Example.instance

Here, the instances a and b of the class Example are essentially the same object. When a value of 007 is assigned to a, the value of b is also the same.

a.val = 007
puts b.val
=> 007

Below is an illustration of the Singleton Design Pattern without using the Singleton library. Here, we can observe that the class Logger's new method has been explicitly declared to be private.

class Logger
 def initialize
   @log = File.open("log.txt", "a")
 end
  
 @@instance = Logger.new

 def self.instance
   return @@instance
 end

 def log(msg)
   @log.puts(msg)
 end

 private_class_method :new
end

Logger.instance.log('message 1')

In this code example, inside class Logger we create instance of the very same class Logger and we can access that instance with class method Logger.instance whenever we need to write something to the log file using the instance method "log". In the initialize method we just opened a log file for appending, and at the end of Logger class, we made method "new" private so that we cannot create new instances of class Logger. And, that is Singleton Pattern: only one instance, globally available.

Adapter Pattern

An Adapter Design Pattern, also known as the Wrapper Pattern enables classes with incompatible interfaces to work together, by providing the users with its interface. This is achieved by:

  • “Wrapping” its own interface around the interface of a pre-existing class.
  • Besides, it may also translate data formats from the caller to a form needed by the callee. Example: If the caller stores boolean value in terms of integers but the callee needs the values as 'true/false', the adapter pattern would extract the right value from the caller and pass it on to the callee. This ensures that the caller and callee can work together.

The purpose of an adapter is “to convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.”

Below is an illustration of the Adapter Pattern:


Types of Adapter Patterns

Based on the structure, there are two types of Adapter Patterns:

Object Adapter Pattern

The Adapter Pattern has an instance of the classit "wraps" and makes calls to this wrapped object alone.

Class Adapter Pattern

Polymorphic Interfaces are used by this type of pattern, where the interface that is expected and the interface that is available are both inherited or implemented. This is used in situations where the class providing the services you need does not provide the interface required.

Command Pattern

At times, it is needed to pass around code that needs to be executed later. This is achieved by employing the Command Design Pattern which is used to factor out the action code of a class into its own object. The Command Pattern is implemented using Closures. A Command class holds an object, method along with the information needed to call a method at a later time. This information includes the method name with the values for the method parameters. The 'Call' method of Ruby brings the different parts together when needed.

This is illustrated using the following example:

One example is a check at a restaurant.

  • The waiter/waitress takes an order from a customer and writes it on a check.
  • The check is then queued for the cook, who prepares the food as requested by the customer.
  • The check is later returned to the server, who uses it to bill the customer.

In this example, the check has nothing to do with the menu. In principle, the same checks could be used at any restaurant.

The command pattern is made up of a client, invoker and receiver. A client creates an object of the Command class and provides the information required to call the method at a later time. The invoker decides when the method should be called. The receiver is the class object which contains the code of the method. The usage of command objects makes it easier to construct components which need to execute methods at a later time without having knowledge about the method's owner or parameters.

This Pattern can be implemented using the Proc objects, which is a callable block of code that closes over the variables in scope when it was created. This gives us a concise implementation of Command Pattern.

Below is an illustration of the Command Design Pattern, using Proc Objects:

count = 0
commands = []
(1..10).each do |i|
commands << proc { count += i }
end
puts "Count is initially #{count}"
commands.each { |cmd| cmd.call }
puts "Performed all commands. count is #{count}"

Another important use of the Command Pattern is to enable the client undo what he has already done, or redo what has been undone by the user. This is done by maintaining a history of the commands executed. As and when the user makes changes, the system creates command after command, executing each command immediately to effect the change. Every undo - able command holds two methods - the execute and the unexecute method. The commands are stored in a list and when the user decides to undo a change, the last command on the list is executed to undo the change. The changes can be undone, by going back the history of commands. The redo is done in the similar way where the the commands are re-executed beginning from the last change that what undone to reapply the changes undone. A simple example of the undo - redo use of a Command Pattern is a Calculator with many undo - redo options.

Algorithm Strategy Pattern

The Strategy Pattern helps choose an algorithm to accomplish a task based on some "parameters" of the situaton. 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". For instance, a class that performs validation on incoming data may use a strategy pattern to select a validation algorithm based on the type of data, the source of the data, user choice, and/or other discriminating factors. These factors are not known for each case until run-time, and may require radically different validation to be performed. The validation strategies, encapsulated separately from the validating object, may be used by other validating objects in different areas of the system (or even different systems) without code duplication. The essential requirement in the programming language is the ability to store a reference to some code in a data structure and retrieve it. This can be achieved by mechanisms such as the native function pointer, the first-class function, classes or class instances in object-oriented programming languages, or accessing the language implementation's internal storage of code via reflection.

References

http://designpatternsinruby.com/section01/article.html