Gof Patterns in Ruby
Gof Patterns in Ruby
Gof Patterns in Ruby
Creational Patterns
• Abstract Factory
• Builder
• Factory Method # can be deduced from Abstract Factory
• Prototype
• Singleton # available in standard lib (doc in singleton.rb)
Structural Patterns
• Adapter
• Bridge # can be deduced from Abstract Factory
• Composite
• Decorator
• Facade # boring and trivial
• Flyweight
• Proxy
Behavioral Patterns
• Chain of Responsibility
• Command
• Interpreter # skipped
• Iterator # built-in (module Enumerable)
• Mediator # skipped
• Memento
• Observer # built-in (doc in observer.rb)
• State # nice implementation by maurice codik
• Strategy
• Template Method # the simplest is the block yielded to
• Visitor
1/33
GoF patterns in Ruby
#
# The GoF Abstract Factory pattern
# written by Matthieu Tanguay-Carel
#
# Factories behave in effect like singletons.
# Extra functionality can be tested for with "Object#respond_to? :extra"
# if needed (See GTKFactory).
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
module MyAbstractFactory
def create_button
raise NotImplementedError, "You should implement this method"
end
end
class Win95Factory
include MyAbstractFactory
def create_button
puts "I'm Win95"
"win95button"
end
end
class MotifFactory
include MyAbstractFactory
def create_button
puts "I'm Motif"
"motifbutton"
end
end
class GTKFactory
include MyAbstractFactory
def create_button
puts "I'm GTK"
"gtkbutton"
end
def extra
puts "I'm enhanced"
end
end
class LookAndFeelManager
@@types2classes = {
:motif => [MotifFactory,nil],
:gtk => [GTKFactory,nil],
:win95 => [Win95Factory,nil]
}
2/33
GoF patterns in Ruby
factory_and_instance = @@types2classes[type]
if factory_and_instance[1].nil?
puts 'instantiating new factory'
factory_and_instance[1] = factory_and_instance[0].new #mutex this
else
puts 'returning already instantiated factory'
factory_and_instance[1]
end
end
end
if __FILE__ == $0
factory = LookAndFeelManager.create :gtk
puts factory.create_button
factory.extra if factory.respond_to? :extra
end
Output
------
3/33
GoF patterns in Ruby
#
# The GoF Builder pattern
# written by Matthieu Tanguay-Carel
#
# The Director class declares the creation process.
# The Builder classes are the concrete builders.
# The builders are free to implement a method or not, and can be
# customised at will by the client.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
class Director
def initialize
@process = [:create_header, :create_body, :create_footer]
end
def build builder
@process.inject("") {|acc, method|
acc += builder.send method if builder.respond_to? method
acc
}
end
end
class HTMLBuilder
def initialize title
@title = title
end
def create_header
"<html><title>#{@title}</title>"
end
def create_body
"<body>fig leave</body>"
end
def create_footer
"</html>"
end
end
class XMLBuilder
def create_header
"<?xml version='1.0' charset='utf-8'?>"
end
def create_body
"<root>welcome</root>"
end
end
if __FILE__ == $0
director = Director.new
html_builder = HTMLBuilder.new 'xml sucks'
puts director.build(html_builder)
4/33
GoF patterns in Ruby
xml_builder = XMLBuilder.new
puts director.build(xml_builder)
end
Output
------
5/33
GoF patterns in Ruby
#
# The GoF Prototype pattern
# written by Matthieu Tanguay-Carel
#
# The Note and Clef classes are the prototypes.
# The deep copy used here will not work if the instances
# have singleton methods (the ruby meaning of singleton).
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
class PrototypeManager
def initialize
@prototypes = {}
end
class Object
def deep_copy
Marshal.load(Marshal.dump(self))
end
end
class Note
attr_accessor :duration
def initialize duration
@duration = duration
end
end
class Clef
attr_accessor :type
def initialize type
@type = type
end
end
if __FILE__ == $0
manager = PrototypeManager.new
manager.register(:half_note, Note.new(2))
manager.register(:full_note, Note.new(4))
6/33
GoF patterns in Ruby
Output
------
7/33
GoF patterns in Ruby
#
# The GoF Adapter pattern
# written by Matthieu Tanguay-Carel
#
# The Adapter offers exactly the same interface as the adaptee, but it can
# override any method or add new ones.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
class Adaptee
def talk
puts "I'm Adaptee"
end
def whine
puts "Stop bullying me!"
end
end
class Adapter
def initialize
@adaptee = Adaptee.new
end
def do_other_stuff
puts "I'm versatile"
end
if __FILE__ == $0
adapter = Adapter.new
adapter.talk
adapter.whine
adapter.do_other_stuff
end
Output
------
8/33
GoF patterns in Ruby
9/33
GoF patterns in Ruby
#
# The GoF Composite pattern
# written by Matthieu Tanguay-Carel
#
# The Component module contains the common behavior between the leaf
# and composite. The component being a module, two classes are free to
# share the same interface without being in the same object hierarchy.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
def children
@children ||= []
end
def to_s
@name
end
class MyFile
include Component
attr_accessor :file_type
class MyDir
include Component
10/33
GoF patterns in Ruby
attr_accessor :icon
if __FILE__ == $0
#setup
root = MyDir.new 'root', :ginger
puts "created directory root with icon in the form of a #{root.icon}"
music = MyDir.new 'music', :clef
jewel = MyDir.new 'jewel', :guitar
notes = MyFile.new 'notes', :text
puts "created file notes whose file type is #{notes.file_type}"
movie = MyFile.new 'ratatouille', :mpeg
todos = MyFile.new 'todos', :text
song = MyFile.new 'iloveyou', :mp3
#use case 1
puts 'prefixing all components as if they were the same type'
def recursive_prefix prefix, component
component.rename(prefix + component.name)
component.children.each {|child|
recursive_prefix prefix, child
}
end
recursive_prefix 'prefixed_', root
#use case 2
puts "extracting all directories"
def all_directories root
root.children.inject([]){|acc,component|
if component.respond_to? :is_dir
acc << component
acc.push *all_directories(component)
end
acc
}
end
all_directories(root).each {|d| puts d}
#use case 3
puts "going up the hierarchy"
def get_master component
component = component.owner while !component.owner.nil?
component
end
11/33
GoF patterns in Ruby
puts get_master(song)
puts get_master(jewel)
end
Output
------
12/33
GoF patterns in Ruby
#
# The GoF Decorator pattern
# written by Matthieu Tanguay-Carel
#
# This pattern is made trivial by Ruby's meta methods.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
module Bordered
attr_accessor :color
attr_accessor :width
end
module Scrollable
def position
@position ||= 0
end
def scroll offset
@position = position + offset
end
end
class Widget
attr_accessor :content
def initialize content
@content = content
end
end
if __FILE__ == $0
widget = Widget.new "flagada jones"
widget.extend(Bordered)
widget.color = :blue
widget.extend(Scrollable)
widget.scroll 3
puts widget.kind_of?(Scrollable)
puts widget.kind_of?(Bordered)
end
Output
------
true
true
13/33
GoF patterns in Ruby
#
# The GoF Flyweight pattern
# written by Matthieu Tanguay-Carel
#
# The Glyph instances are the flyweights.
# Each glyph knows how to draw itself, given the context.
# You can supply a block to Glyp#draw to draw something else than
# the glyph itself.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
class Glyph
attr_accessor :char
def initialize char
puts "initializing with #{char}"
@char = char
end
def draw context #hash expecting :color and :size and :x and :y as keys
inner_html = block_given?? yield(@char) : @char
"<span style='color:#{context[:color]}; font-size:#{context[:size]};\
position:absolute; top: #{context[:y]}px; " + \
" left: #{context[:x]}px'>#{inner_html}</span>"
end
end
class FlyweightFactory
def initialize
@flyweights = {}
end
if __FILE__ == $0
#a few tests
factory = FlyweightFactory.new
a = factory.get :a
a2 = factory.get :a
puts "Flyweights are the same object: #{a.eql?(a2)}"
b = factory.get :b
b2 = factory.get :b
puts "Flyweights are the same object: #{b.eql?(b2)}"
14/33
GoF patterns in Ruby
index = rand 3
index2 = rand 3
x = rand 800
y = rand 600
context[:color] = colors[index]
context[:size] = sizes[index2]
context[:x] = x
context[:y] = y
file.write factory.get(s).draw(context) {|char|
"#{char}?!"
}
}
}
end
Output
------
initializing with a
Flyweights are the same object: true
initializing with b
Flyweights are the same object: true
initializing with c
initializing with d
initializing with e
initializing with f
15/33
GoF patterns in Ruby
#
# The GoF Proxy pattern
# written by Matthieu Tanguay-Carel
#
# The Image class is the proxy. It should override the operations
# the clients need before costly processing has to take place.
# The attr_proxy method allows the Proxy module to automatically
# remove the overridden methods once the real subject is created.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
module Proxy
def self.included cls
puts "creating the attr_proxy method"
cls.instance_eval {
def proxy_methods
@proxy_methods ||= []
end
def attr_proxy name
proxy_methods << name
end
}
end
def real_subject
@real_subject or nil
end
class Image
include Proxy
16/33
GoF patterns in Ruby
attr_accessor :mtime
attr_proxy :mtime
attr_proxy :mtime=
attr_proxy :to_s
def to_s
"proxy_image"
end
end
if __FILE__ == $0
#create the proxy
img = Image.new
img.proxy(File, ["img.jpg", 'w'])
img.mtime = "a few hours ago"
puts "proxy methods:"
img.class.proxy_methods.each {|m| puts m}
puts ''
Output
------
17/33
GoF patterns in Ruby
#
# The GoF Chain of Responsibility pattern
# written by Matthieu Tanguay-Carel
#
# Each handler needs to be added to a chain and needs to be given
# an operation.
# The handler's operation is a block that should return false if the request
# should be sent forward.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
class Array
def element_after item
inject(false){|acc, elem|
return elem if acc
elem == item ? true : false
}
nil
end
end
class Chain
def initialize
@chain = []
end
module Handler
attr_accessor :chain
def handle request
raise Exception.new("Handler without a chain") if @chain.nil?
raise Exception.new("Handler without an operation") if @operation.nil?
chain.forward self, request if !@operation.call request
end
def protect
begin
18/33
GoF patterns in Ruby
yield
rescue Exception
puts $!
end
end
if __FILE__ == $0
@chain = Chain.new
coward = Object.new
coward.extend Handler
coward.operation {|request|
puts "I'm not getting my hands dirty. Let's forward."
false
}
hard_worker = Object.new
hard_worker.extend Handler
hard_worker.operation {|request|
if request.respond_to? :include? and request.include? "work"
puts "Request handled!"
true
else
puts "Could not handle request... forwarding."
false
end
}
#tests
protect {
puts "\nSending first test request"
coward.handle "test"
}
protect {
puts "\nSending work request"
coward.handle "work"
}
puts "\nMaking it fail"
foreigner = Object.new
foreigner.extend Handler
protect { foreigner.handle "me" }
foreigner.operation {|request| puts "Guten Tag"}
protect { @chain.forward foreigner, "hehe" }
end
19/33
GoF patterns in Ruby
Output
------
Making it fail
Handler without a chain
End of chain: caller has no forward neighbor available in this chain
20/33
GoF patterns in Ruby
#
# The GoF Command pattern
# written by Matthieu Tanguay-Carel
#
# The Command instance is initialized with its receiver.
# Commands can be grouped by registering children to a macro command.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
class Command
attr_accessor :receiver
def initialize receiver
@receiver = receiver
@commands = []
end
def execute
@commands.each {|cmd| cmd.save }
@commands.each {|cmd| cmd._execute }
save
_execute
end
def undo
@commands.each {|cmd| cmd.undo }
end
def _execute
end
end
21/33
GoF patterns in Ruby
end
end
module Invoker
attr_accessor :command
def click
@command.execute
end
def undo
@command.undo
end
end
class Document
attr_accessor :text
def initialize text
@text = text
end
end
if __FILE__ == $0
text = "This is a test"
doc = Document.new text
upcase_cmd = UppercaseCommand.new doc
button = Object.new.extend(Invoker)
button.command = upcase_cmd
big_button = Object.new.extend(Invoker)
big_button.command = allCmds
puts "before anything"
puts doc.text
big_button.click
puts "after click"
puts doc.text
22/33
GoF patterns in Ruby
big_button.undo
puts "after undo"
puts doc.text
end
Output
------
before anything
This is a test
after click
THIS IS A TEST
after undo
This is a test
23/33
GoF patterns in Ruby
#
# The GoF Memento pattern
# written by Matthieu Tanguay-Carel
#
# The Originator can save and load itself.
# The Caretaker (the main function in this case) never has to touch
# the memento objects.
#
# This implementation is a bit naive:
# - saves should be kept in files
# - Marshal will not always work (singleton methods, bindings, etc..)
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
module Originator
def saves
@saves ||= {}
end
class Example
include Originator
attr_accessor :name, :color
if __FILE__ == $0
ex = Example.new "Matt", "blue"
puts "my name is #{ex.name}"
ex.save :now
ex.name = "John"
puts "my name is #{ex.name}"
ex.save :later
ex.restore :now
24/33
GoF patterns in Ruby
Output
------
my name is Matt
saving key now
my name is John
saving key later
restoring key now
my name is Matt
restoring key later
my name is John
25/33
GoF patterns in Ruby
#
# The GoF State pattern
#
# Here is Maurice Codik's implementation.
# I only added an "if __FILE__ == $0", tweaked the layout, and fixed
# a typo.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Copyright (C) 2006 Maurice Codik - maurice.codik@gmail.com
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# Each call to state defines a new subclass of Connection that is stored
# in a hash. Then, a call to transition_to instantiates one of these
# subclasses and sets it to the be the active state. Method calls to
# Connection are delegated to the active state object via method_missing.
module StatePattern
class UnknownStateException < Exception
end
def StatePattern.included(mod)
mod.extend StatePattern::ClassMethods
end
module ClassMethods
attr_reader :state_classes
def state(state_name, &block)
@state_classes ||= {}
26/33
GoF patterns in Ruby
@state_classes[state_name] = new_klass
end
end
klass = new_context.class.state_classes[state_name]
if klass
new_context.current_state = state_name
new_context.current_state_obj = klass.new(new_context, *args, &block)
else
raise UnknownStateException, "tried to transition to " + \
"unknown state, #{state_name}"
end
end
end
class Connection
include StatePattern
state :initial do # you always need a state named initial
def connect
puts "connected"
# move to state :connected. all other args to transition_to
# are passed to the new state's constructor
transition_to :connected, "hello from initial state"
end
def disconnect
puts "not connected yet"
end
end
state :connected do
def initialize(msg)
puts "initialize got msg: #{msg}"
end
def connect
puts "already connected"
end
def disconnect
puts "disconnecting"
transition_to :initial
end
27/33
GoF patterns in Ruby
end
def reset
puts "resetting outside a state"
# you can also change the state from outside of the state objects
transition_to :initial
end
end
if __FILE__ == $0
c = Connection.new
c.disconnect # not connected yet
c.connect # connected
# initialize got msg: hello from initial state
c.connect # already connected
c.disconnect # disconnecting
c.connect # connected
# initialize got msg: hello from initial state
c.reset # reseting outside a state
c.disconnect # not connected yet
end
Output
------
28/33
GoF patterns in Ruby
#
# The GoF Strategy pattern
# written by Matthieu Tanguay-Carel
#
# Sorter is the Context object. It allows to choose between sorting
# implementations.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
class QuickSort
def sort arr
return [] if arr.length == 0
x, *xs = *arr
smaller, bigger = xs.partition{ |other| other < x }
sort(smaller) + [x] + sort(bigger)
end
end
class MergeSort
def sort array
if array.length <= 1
return array
end
middle = array.length / 2
left = array[0...middle]
right = array[middle...array.length]
left = sort left
right = sort right
return merge(left,right)
end
class Sorter
@@default_strategy = QuickSort.new
def self.sort arr, strategy=nil
strategy ||= @@default_strategy
strategy.sort(arr)
end
end
29/33
GoF patterns in Ruby
arr = []
size.times do arr << rand(100) end
arr
end
require 'benchmark'
if __FILE__ == $0
arr_length = 1000
arr1 = get_random_array arr_length
puts "Sorting first array"
#print_elems arr1
puts "Time taken for QuickSort: #{Benchmark.measure {
arr1 = Sorter.sort(arr1, QuickSort.new)
print_elems arr1[0...40]
}}"
Output
------
30/33
GoF patterns in Ruby
#
# The GoF Template pattern
# written by Matthieu Tanguay-Carel
#
# The module Template implements the boilerplate of the algorithm.
# Some hooks are optional and some mandatory.
#
# Of course you could also just yield to a block if your template is simple.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
module Template
#mandatory_methods = ["tagname", "content"]
#optional_methods = ["font_size", "background_color"]
def generate
str = "<#{tagname}"
styles = ''
styles += "font-size:#{font_size};" if respond_to? :font_size
styles += "background-color:#{background_color};" \
if respond_to? :background_color
str += " style='#{styles}'" if !styles.empty?
str += ">#{content}</#{tagname}>"
end
end
class Body
def tagname
"body"
end
def content
"hello"
end
def font_size
"18pt"
end
include Template
end
if __FILE__ == $0
b = Body.new
puts b.generate
end
Output
------
<body style='font-size:18pt;'>hello</body>
31/33
GoF patterns in Ruby
#
# The GoF Visitor pattern
# written by Matthieu Tanguay-Carel
#
# Depends on Rubytree (gem install rubytree).
# The Node module contains the whole logic. A visitor can only implement
# the callbacks it is interested in.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
require 'rubygems'
require 'tree'
module Node
def accept visitor
if self.kind_of? StringNode
visitor.visit_string self if visitor.respond_to? :visit_string
elsif self.kind_of? IntegerNode
visitor.visit_int self if visitor.respond_to? :visit_int
end
end
end
class StringNode
include Node
attr_accessor :string
def initialize val
@string = val
end
end
class IntegerNode
include Node
attr_accessor :int
def initialize val
@int = val
end
end
class PrintingVisitor
def visit_string node
puts node.string
end
def visit_int node
puts node.int
end
end
class RevertingVisitor
def visit_string node
puts node.string.reverse!
end
end
if __FILE__ == $0
myTreeRoot = Tree::TreeNode.new("ROOT", StringNode.new("this is the root node"))
32/33
GoF patterns in Ruby
Output
------
PRINTING visitor...
this is the root node
madam im adam
3
2
race car
damn, i agassi miss again. mad
REVERTING visitor...
edon toor eht si siht
mada mi madam
rac ecar
dam .niaga ssim issaga i ,nmad
33/33