CSC/ECE 517 Fall 2007/wiki1b 3 an: Difference between revisions
No edit summary |
|||
(45 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
''' | ''We have said that closures in Ruby can be used to implement the Command and Strategy patterns. Closures are useful in implementing various other design patterns too. Explain which patterns, and give examples, using pseudo-Ruby code (i.e., your code should illustrate how to solve the problem with closures in Ruby, but it doesn't actually have to run, if it would be too complicated to give a running example). '' | ||
__TOC__ | |||
==Closures== | |||
=== What are Closures?=== | |||
Every ruby block is a closure in the sense that it carries around the context in which it was defined. This means that a closure can reference the variables that were in scope when it was defined, even if those variables later go out of scope. | |||
def closure | |||
x = "a_local_variable" | |||
return proc { puts x } # when this line gets executed, the proc takes a snapshot | |||
# of the embedding scope: in this case a local variable | |||
end | |||
closure.call # => a_local_variable | |||
This enables us to move around these blocks throughout our code without having to worry about the context. In addition to that, we can also pass a closure to another method which enables us to customize the behavior of the method. | |||
Thus a closure is a block of code that has these three properties | |||
• It can be passed around as a value. | |||
• It can be executed on demand by any procedure or method that has that value. | |||
• It can refer to variables from the context in which it was created (it is “closed” with respect to variable access). | |||
=== A Simple Closure Example === | |||
def remember(&a_block) | |||
@block = a_block | |||
end | |||
# Invoke the above method, giving it a block that takes a name. | |||
remember {|name| puts "Hello, #{name}!"} | |||
# When the time is right (for the object) -- call the closure! | |||
@block.call("John") | |||
# => "Hello, John!" | |||
=== Advantages of Closures === | |||
• Designers of software libraries can allow users to customize behavior by passing closures as arguments to important functions.For example, a function that sorts values can accept a closure argument that compares the values to be sorted according to a user-defined criterion. | |||
• Because closures delay evaluation—i.e., they do not "do" anything until they are called—they can be used to define control structures. | |||
== Closures And Design Patterns == | |||
When implementing design patterns, it is often useful to pass around code that needs to be executed later.Using the power of closures,a number of design patterns can be implemented in Ruby in a simpler way as compared to other languages.Command and Strategy patterns are usually implemented using proc objects which are nothing but closures.In the following sections we discuss the use of closures in implementing Observer and Iterator patterns along with pseudocodes for the same. | |||
== Observer Pattern == | |||
=== Description === | |||
The observer pattern is used to observe the state of an object in a program. An observer pattern defines a one to many dependency between objects so that when one object changes state all its dependents are notified and updated accordingly | |||
=== Typical Uses=== | |||
Observer pattern is mainly used to implement a distributed event handling system where objects are listening for an external event or for changes of the value of an object property. | |||
=== Examples === | |||
• In a mailing list,where every time an event happens (a new product,a gathering etc) a message is sent to the people subscribed to the list. | |||
• A change in the data values triggers change in the bar graph pie chart representations in a spreadsheet. | |||
===Terminology=== | |||
In its simplified form, the observer design pattern consists of a subject,and one or more observers,which observe an event in the subject.An event can be a change in the state of the subject and can be defined accordingly. | |||
'''Subject''' | |||
This class provides an interface for attaching and detaching observers.Contains the following functions | |||
• Attach – Adds a new observer to the list of observers observing the subject | |||
• Detach – Deletes an observer from the list of observers observing the subject | |||
• Notify – Notifies each observer by calling the notify function in the observer,whenever a change occurs. | |||
'''ConcreteSubject''' | |||
class | This class provides the state of interest to observers. It also sends a notification to all observers, by calling the Notify function in its super class (i.e, in the Subject class). It contains the following function: | ||
GetState - Returns the state of the subject | |||
'''Observer''' | |||
This class defines an updating interface for all observers, to receive update notification from the subject. The Observer class is used as an abstract class to implement concrete observers. It contains the following function: | |||
Notify - An abstract function, to be overridden by concrete observers. | |||
'''ConcreteObserver''' | |||
This class maintains a reference with the ConcreteSubject, to receive the state of the subject when a notification is received. It contains the following function: | |||
class | Notify - This is the overridden function in the concrete class. When this function is called by the subject, the ConcreteObserver calls the GetState function of the subject to update the information it has about the subject's state. | ||
===Structure === | |||
[[Image:observer.jpg]] | |||
==Closures and Observer Pattern== | |||
Closures can be used to implement Observer pattern.The role of closures can be the basis of the following two applications of Observer design patterns | |||
• A change in one object requires changing other objects.Also the number of objects to be changed is unknown. | |||
• The object being changed should be able to notify other objects about the change without having any idea about the current state of the objects. | |||
===Pseudocode=== | |||
class Subject | |||
def initialize | |||
@list=[] | |||
end | |||
def attach(&list) | |||
@list << list | |||
end | |||
def notify | |||
@list.each{|x| x.call(@radius)} | |||
end | |||
def update(r) | |||
@radius=r | |||
notify | |||
end | |||
end | |||
class Observer1 | |||
def calc_area(radius) | |||
@area=radius*radius*3.14 | |||
puts "Area #{@area}" | |||
end | |||
end | |||
class Observer2 | |||
def calc_circumference(radius) | |||
@circum=2*3.14*radius | |||
puts "Circumference #{@circum}" | |||
end | |||
end | |||
o1=Observer1.new | |||
o2=Observer2.new | |||
s=Subject.new | |||
s.attach { |s| o1.calc_area(s)} | |||
s.attach { |s| o2.calc_circumference(s)} | |||
s.update(5) | |||
Circumference 31.4 | The output is | ||
Area 78.5 | |||
Circumference 31.4 | |||
Here we have a subject class that contains the following methods. | Here we have a subject class that contains the following methods. | ||
• Attach() | |||
This method is used to update the list of observers | |||
• Update() | |||
This method updates the radius and calls the notify method. | |||
• Notify() | |||
This method notifies all the observers in the list about the event which is the change in the value of the radius. | |||
We have two observers for calculating area and circumference. | We have two observers for calculating area and circumference. | ||
Line 114: | Line 174: | ||
== Iterator Pattern == | |||
=== Description === | |||
The Iterator pattern is a design pattern in which iterators are used to access the elements of an aggregate object sequentially without exposing its underlying representation. An Iterator object encapsulates the internal structure of how the iteration occurs.An aggregate function can be an array or a hash. | |||
=== Typical Uses=== | |||
• to access an aggregate object's contents without exposing its internal representation. | |||
• to support multiple traversals of aggregate objects. | |||
• to provide a uniform interface for traversing different aggregate structures | |||
=== Examples === | |||
A tree, linked list, hash table, and an array all need to be iterated with Search, Sort, Next.Using the iterator pattern will reduce the number of redundant procedures written for each aggregate object. | |||
===Terminology=== | |||
• Iterator -defines an interface for accessing and traversing elements. | |||
• ConcreteIterator -implements the Iterator interface and keeps track of the current position in the traversal of the aggregate. | |||
• Aggregate - defines an interface for creating an Iterator object. | |||
• ConcreteAggregate - implements the Iterator creation interface to return an instance of the proper ConcreteIterator. | |||
===Structure === | |||
[[Image:iterator.jpg]] | |||
==Closures and Iterator Pattern== | |||
===Pseudocode 1=== | |||
Following is a simple example which illustrates the iterator pattern using the power of Ruby closures. | |||
def fibonacci() | |||
while x <= max | |||
yield x | |||
x, y = y, x + y | |||
end | |||
end | |||
fibonacci 30 { |x| print x, " " } # -> 1 1 2 3... | |||
y = 0 | |||
fibonacci 30 {|x| y += x } | |||
puts y # -> 54 | |||
In this code the function fibonacci is used to print out the elements of the series. The number of elements to be printed depends on the argument x. The function is also used to calculate the sum of the x elements. Closures are used to implement both the functionalities as shown. The variables x and y are referenced even after they go out of scope. | |||
===Pseudocode 2=== | |||
Here is another pseudocode which illustrates an implementation of the iterator pattern using closures. | |||
def find_birthdays_in_range(min,max) | |||
birthdays = [ "1970-06-15", "1975-08-13", "1939-03-21", "2001-12-01" ] | |||
birthdays.find_all { |birthday| birthday > min && birthday < max } | |||
end | |||
Here there are 3 functions here that are called from within each other. | |||
1 : find_birthdays_in_range | |||
2 : find_all | |||
3 : anonymous block | |||
The third function is the closure.It accepts one parameter called birthday which is defined in the first function.It then iterates over the array(birthdays) and returns only those values in the given range,specified by min and max. | |||
==References== | |||
[http://www.rubycentral.com/pickaxe/ 1. Programming Ruby:The Pragmatic Programmers Guide] | |||
[http://www.amazon.com/Head-First-Design-Patterns/dp/0596007124 2. Head First Design Patterns] | |||
[http://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional/dp/0201633612 3. Design Patterns - Elements Of Reusable Object Oriented Software] | |||
[http://www.oreilly.com/catalog/rubyckbk/ 4. Ruby Cookbook] | |||
==External Links== | |||
[http://www.ruby-doc.org/core/ 1. Ruby Standard Library Documentation] | |||
[http://www.zorched.net/2006/02/19/ruby-features-youll-wish-you-had-in-other-languages/ 2. Features of Ruby] | |||
[http://www.artima.com/intv/closures.html 3. Closures in Ruby ] | |||
[http://cwilliams.textdriven.com/articles/2006/11/02/patterns-in-ruby-observer-pattern 4. Observer pattern in Ruby] | |||
[http:// |
Latest revision as of 03:25, 11 October 2007
We have said that closures in Ruby can be used to implement the Command and Strategy patterns. Closures are useful in implementing various other design patterns too. Explain which patterns, and give examples, using pseudo-Ruby code (i.e., your code should illustrate how to solve the problem with closures in Ruby, but it doesn't actually have to run, if it would be too complicated to give a running example).
Closures
What are Closures?
Every ruby block is a closure in the sense that it carries around the context in which it was defined. This means that a closure can reference the variables that were in scope when it was defined, even if those variables later go out of scope.
def closure x = "a_local_variable" return proc { puts x } # when this line gets executed, the proc takes a snapshot # of the embedding scope: in this case a local variable end closure.call # => a_local_variable
This enables us to move around these blocks throughout our code without having to worry about the context. In addition to that, we can also pass a closure to another method which enables us to customize the behavior of the method.
Thus a closure is a block of code that has these three properties
• It can be passed around as a value.
• It can be executed on demand by any procedure or method that has that value.
• It can refer to variables from the context in which it was created (it is “closed” with respect to variable access).
A Simple Closure Example
def remember(&a_block) @block = a_block end # Invoke the above method, giving it a block that takes a name. remember {|name| puts "Hello, #{name}!"} # When the time is right (for the object) -- call the closure! @block.call("John") # => "Hello, John!"
Advantages of Closures
• Designers of software libraries can allow users to customize behavior by passing closures as arguments to important functions.For example, a function that sorts values can accept a closure argument that compares the values to be sorted according to a user-defined criterion.
• Because closures delay evaluation—i.e., they do not "do" anything until they are called—they can be used to define control structures.
Closures And Design Patterns
When implementing design patterns, it is often useful to pass around code that needs to be executed later.Using the power of closures,a number of design patterns can be implemented in Ruby in a simpler way as compared to other languages.Command and Strategy patterns are usually implemented using proc objects which are nothing but closures.In the following sections we discuss the use of closures in implementing Observer and Iterator patterns along with pseudocodes for the same.
Observer Pattern
Description
The observer pattern is used to observe the state of an object in a program. An observer pattern defines a one to many dependency between objects so that when one object changes state all its dependents are notified and updated accordingly
Typical Uses
Observer pattern is mainly used to implement a distributed event handling system where objects are listening for an external event or for changes of the value of an object property.
Examples
• In a mailing list,where every time an event happens (a new product,a gathering etc) a message is sent to the people subscribed to the list.
• A change in the data values triggers change in the bar graph pie chart representations in a spreadsheet.
Terminology
In its simplified form, the observer design pattern consists of a subject,and one or more observers,which observe an event in the subject.An event can be a change in the state of the subject and can be defined accordingly.
Subject
This class provides an interface for attaching and detaching observers.Contains the following functions
• Attach – Adds a new observer to the list of observers observing the subject
• Detach – Deletes an observer from the list of observers observing the subject
• Notify – Notifies each observer by calling the notify function in the observer,whenever a change occurs.
ConcreteSubject
This class provides the state of interest to observers. It also sends a notification to all observers, by calling the Notify function in its super class (i.e, in the Subject class). It contains the following function:
GetState - Returns the state of the subject
Observer
This class defines an updating interface for all observers, to receive update notification from the subject. The Observer class is used as an abstract class to implement concrete observers. It contains the following function:
Notify - An abstract function, to be overridden by concrete observers.
ConcreteObserver
This class maintains a reference with the ConcreteSubject, to receive the state of the subject when a notification is received. It contains the following function:
Notify - This is the overridden function in the concrete class. When this function is called by the subject, the ConcreteObserver calls the GetState function of the subject to update the information it has about the subject's state.
Structure
Closures and Observer Pattern
Closures can be used to implement Observer pattern.The role of closures can be the basis of the following two applications of Observer design patterns
• A change in one object requires changing other objects.Also the number of objects to be changed is unknown.
• The object being changed should be able to notify other objects about the change without having any idea about the current state of the objects.
Pseudocode
class Subject def initialize @list=[] end def attach(&list) @list << list end def notify @list.each{|x| x.call(@radius)} end def update(r) @radius=r notify end end
class Observer1 def calc_area(radius) @area=radius*radius*3.14 puts "Area #{@area}" end end
class Observer2 def calc_circumference(radius) @circum=2*3.14*radius puts "Circumference #{@circum}" end end
o1=Observer1.new o2=Observer2.new s=Subject.new s.attach { |s| o1.calc_area(s)} s.attach { |s| o2.calc_circumference(s)} s.update(5)
The output is Area 78.5 Circumference 31.4
Here we have a subject class that contains the following methods.
• Attach()
This method is used to update the list of observers
• Update()
This method updates the radius and calls the notify method.
• Notify()
This method notifies all the observers in the list about the event which is the change in the value of the radius.
We have two observers for calculating area and circumference. On receiving notify,these functions return updated values of area and circumference depending on the new value of the radius.
Iterator Pattern
Description
The Iterator pattern is a design pattern in which iterators are used to access the elements of an aggregate object sequentially without exposing its underlying representation. An Iterator object encapsulates the internal structure of how the iteration occurs.An aggregate function can be an array or a hash.
Typical Uses
• to access an aggregate object's contents without exposing its internal representation.
• to support multiple traversals of aggregate objects.
• to provide a uniform interface for traversing different aggregate structures
Examples
A tree, linked list, hash table, and an array all need to be iterated with Search, Sort, Next.Using the iterator pattern will reduce the number of redundant procedures written for each aggregate object.
Terminology
• Iterator -defines an interface for accessing and traversing elements.
• ConcreteIterator -implements the Iterator interface and keeps track of the current position in the traversal of the aggregate.
• Aggregate - defines an interface for creating an Iterator object.
• ConcreteAggregate - implements the Iterator creation interface to return an instance of the proper ConcreteIterator.
Structure
Closures and Iterator Pattern
Pseudocode 1
Following is a simple example which illustrates the iterator pattern using the power of Ruby closures.
def fibonacci() while x <= max yield x x, y = y, x + y end end
fibonacci 30 { |x| print x, " " } # -> 1 1 2 3... y = 0 fibonacci 30 {|x| y += x } puts y # -> 54
In this code the function fibonacci is used to print out the elements of the series. The number of elements to be printed depends on the argument x. The function is also used to calculate the sum of the x elements. Closures are used to implement both the functionalities as shown. The variables x and y are referenced even after they go out of scope.
Pseudocode 2
Here is another pseudocode which illustrates an implementation of the iterator pattern using closures.
def find_birthdays_in_range(min,max) birthdays = [ "1970-06-15", "1975-08-13", "1939-03-21", "2001-12-01" ] birthdays.find_all { |birthday| birthday > min && birthday < max } end
Here there are 3 functions here that are called from within each other.
1 : find_birthdays_in_range
2 : find_all
3 : anonymous block
The third function is the closure.It accepts one parameter called birthday which is defined in the first function.It then iterates over the array(birthdays) and returns only those values in the given range,specified by min and max.
References
1. Programming Ruby:The Pragmatic Programmers Guide
3. Design Patterns - Elements Of Reusable Object Oriented Software