DESIGN PATTERNS
THE WAY
Hello ConFoo!
I AM FRED HEATH
Developer, Problem
solver, Ruby evangelist,
Agile practitioner.
You can find me at:
@FredAtBootstrap
bootstrap.me.uk
Let’s start with the
first set of slides
The
gospel
according
to Russ
1
STRATEGY
APPLICATIONS
▪ Dynamically choose algorithm
Strategy
class PaymentStrategy
def pay(sum)
raise "can't call that!"
end
end
class PayPalPayment < PaymentStrategy
def pay(sum)
puts ".....paying $#{sum} by PayPal"
end
end
class WorldPayPayment < PaymentStrategy
def pay(sum)
puts ".....paying $#{sum} by WorldPay"
end
end
class BitcoinPayment < PaymentStrategy
def pay(sum)
puts ".....paying $#{sum} by Bitcoin"
end
end
class Purchase
attr_reader :items, :sum
attr_accessor :payment_method
def initialize(items, payment_method)
@payment_method = payment_method
@sum = 0
items.each do |item, value|
@sum += value
end
end
def pay
payment_method.pay(@sum)
end
end
# class PaymentStrategy
# def pay(sum)
# raise "can't call that!"
# end
# end
class PayPalPayment #< PaymentStrategy
def pay(sum)
puts ".....paying $#{sum} by PayPal"
end
end
class WorldPayPayment #< PaymentStrategy
def pay(sum)
puts ".....paying $#{sum} by WorldPay"
end
end
class BitcoinPayment #< PaymentStrategy
def pay(sum)
puts ".....paying $#{sum} by Bitcoin"
end
end
class Purchase
attr_reader :items, :sum
attr_accessor :payment_method
def initialize(items, payment_method)
@payment_method = payment_method
@sum = 0
items.each do |item, value|
@sum += value
end
end
def pay
payment_method.pay(@sum)
end
end
$> purchase = Purchase.new({cd_Wild_Beasts: 5.2, baseball_cap: 8.5 }, WorldPayPayment.
new)
$> purchase.pay
.....paying $13.7 by WorldPay
$> purchase = Purchase.new({cd_Wild_Beasts: 5.2, baseball_cap: 8.5 }, PayPalPayment.
new)
$> purchase.pay
.....paying $13.7 by PayPal
Lambda: an anonymous, first-class
function.
def m(&a_lambda)
a_lambda.call
end
$> m {puts "this is a lambda"}
"this is a lambda"
def m(&a_lambda)
x = 'hello'
a_lambda.call(x)
end
$> m {|x| puts "x is: #{x}"}
"x is: hello"
class PayPalPayment
def pay(sum)
puts ".....paying $#{sum} by
PayPal"
end
end
class WorldPayPayment
def pay(sum)
puts ".....paying $#{sum} by
WorldPay"
end
end
class BitcoinPayment
def pay(sum)
puts ".....paying $#{sum} by
Bitcoin"
end
end
class Purchase
attr_reader :items, :sum
attr_accessor :payment_method
def initialize(items, payment_method)
@payment_method = payment_method
@sum = 0
items.each do |item, value|
@sum += value
end
end
def pay
payment_method.pay(@sum)
end
end
#class PayPalPayment
# def pay(sum)
# puts ".....paying $#{sum} by
PayPal"
# end
#end
#class WorldPayPayment
# def pay(sum)
# puts ".....paying $#{sum} by
WorldPay"
# end
#end
#class BitcoinPayment
# def pay(sum)
# puts ".....paying $#{sum} by
Bitcoin"
# end
#end
class Purchase
attr_reader :items, :sum
attr_accessor :payment_method
def initialize(items, payment_method)
@payment_method = payment_method
@sum = 0
items.each do |item, value|
@sum += value
end
end
def pay
payment_method.pay(@sum)
end
end
#class PayPalPayment
# def pay(sum)
# puts ".....paying $#{sum} by
PayPal"
# end
#end
#class WorldPayPayment
# def pay(sum)
# puts ".....paying $#{sum} by
WorldPay"
# end
#end
#class BitcoinPayment
# def pay(sum)
# puts ".....paying $#{sum} by
Bitcoin"
# end
#end
class Purchase
attr_reader :items, :sum
attr_accessor :payment_method
def initialize(items, &payment_method)
@payment_method = payment_method
@sum = 0
items.each do |item, value|
@sum += value
end
end
def pay
payment_method.call(@sum)
end
end
$> purchase = Purchase.new({cd_Wild_Beasts: 5.2, baseball_cap: 8.5 }) {|sum| puts ".....
paying $#{sum} by WorldPay"}
$> purchase.pay
.....paying $13.7 by WorldPay
$> purchase = Purchase.new({cd_Wild_Beasts: 5.2, baseball_cap: 8.5 }) {|sum| puts ".....
paying $#{sum} by Amazon Payments"}
$> purchase.pay
.....paying $13.7 by Amazon Payments
From:
To:
2
COMMAND
APPLICATIONS
▪ User Interfaces
▪ Queuing/Logging (Wizards)
▪ Do-Undo-Redo
Command
class Command
def do_command
raise "can't do this here"
end
def undo_command
raise "can't do this here"
end
end
class Incrementer < Command
def initialize(aggregate)
@aggregate = aggregate
end
def do_command
@aggregate += 2
end
def undo_command
@aggregate -= 2
end
end
#class Command
# def do_command
# raise "can't do this here"
# end
# def undo_command
# raise "can't do this here"
# end
#end
class Incrementer #< Command
def initialize(aggregate)
@aggregate = aggregate
end
def do_command
@aggregate += 2
end
def undo_command
@aggregate -= 2
end
end
count = 0
commands = []
(1..10).each do |i|
commands << Incrementer.new(count)
end
puts "Count initially is: #{count}"
commands.each {|cmd| cmd.do_command}
puts "Count after doing commands: #{count}
Count initially is: 0
Count after doing commands: 0
Closure: A first-class function
that has lexical scope.
outer = 1
def m a_var
inner = 99
puts "inner var = #{inner}"
proc {inner + a_var}
end
p = m(outer)
puts "p is a #{p.class}"
puts "result of proc call: #{p.call}"
inner var = 99
p is a Proc
result of proc call: 100
outer = 1
def m a_var
inner = 99
puts "inner var = #{inner}"
proc {inner + a_var}
end
p = m(outer)
puts "p is a #{p.class}"
outer = 0
puts "changed outer to #{outer}"
puts "result of proc call: #{p.call}"
inner var = 99
p is a Proc
changed outer to 0
result of proc call: 100
class Command
attr_accessor :cmd, :uncmd
def initialize(do_command, undo_command)
@cmd = do_command
@uncmd = undo_command
end
def do_command
@cmd.call
end
def undo_command
@uncmd.call
end
end
count = 0
commands = []
(1..10).each do |i|
commands << Command.new(proc {count += 2}, proc {count -= 2})
end
puts "Count initially is: #{count}"
commands.each {|cmd| cmd.do_command}
puts "Count after doing commands: #{count}"
Count initially is: 0
Count after doing commands: 20
count = 0
commands = []
(1..10).each do |i|
commands << Command.new(proc {count += 2}, proc {count -= 2})
end
puts "Count initially is: #{count}"
commands.each {|cmd| cmd.do_command}
puts "Count after doing commands: #{count}"
commands.reverse_each {|cmd| cmd.undo_command}
puts "Count after un-doing commands: #{count}"
commands.each {|cmd| cmd.do_command}
puts "Count after re-doing commands: #{count}"
Count initially is: 0
Count after doing commands: 20
Count after un-doing commands: 0
Count after re-doing commands: 20
From:
To:
3
PROXY
APPLICATIONS
▪ Protection
▪ Remote Access
▪ Lazy Creation (Virtual Proxy)
Proxy
class Car
def drive
raise "use the Proxy instead"
end
end
class RealCar < Car
def drive
puts "vroom,vroom..."
end
end
class ProxyCar < Car
def initialize(real_car, driver_age)
@driver_age = driver_age
@real_car = real_car
end
def check_access
@driver_age > 16
end
def get_real_car
@real_car || (@real_car = Car.new
(@driver_age))
end
def drive
car = get_real_car
check_access ? car.drive : puts("Sorry,
you're too young to drive")
end
end
class RealCar
def drive
puts "vroom,vroom..."
end
end
class ProxyCar
def initialize(real_car, driver_age)
@driver_age = driver_age
@real_car = real_car
end
def check_access
@driver_age > 16
end
def get_real_car
@real_car || (@real_car = Car.new
(@driver_age))
end
def drive
car = get_real_car
check_access ? car.drive : puts("Sorry,
you're too young to drive")
end
end
class RealCar
def drive
puts "vroom,vroom..."
end
end
class Client
attr_reader :age
def initialize(age)
@age = age
end
def drive(car)
car.drive
end
end
class ProxyCar
def initialize(real_car, driver_age)
@driver_age = driver_age
@real_car = real_car
end
def check_access
@driver_age > 16
end
def get_real_car
@real_car || (@real_car = Car.new
(@driver_age))
end
def drive
car = get_real_car
check_access ? car.drive : puts("Sorry,
you're too young to drive")
end
end
tom = Client.new(25)
car = RealCar.new()
proxy = ProxyCar.new(car, tom.age)
tom.drive(proxy)
vroom,vroom...
tom = Client.new(15)
car = RealCar.new()
proxy = ProxyCar.new(car, tom.age)
tom.drive(proxy)
Sorry, you're too young to drive
Dynamic Dispatching: selecting
which method to call at run-time
puts [1, 2, 3].reverse
3
2
1
puts [1, 2, 3].send(:reverse)
3
2
1
BasicObject
Kernel
Object
MyClass
SuperClass
………..
………..
a_method
BasicObject
Kernel
Object
MyClass
SuperClass
………..
………..
a_method
BasicObject
Kernel
Object
MyClass
SuperClass
………..
………..
a_method
BasicObject
Kernel
Object
MyClass
SuperClass
………..
………..
a_method
BasicObject
Kernel
Object
MyClass
SuperClass
………..
………..
a_method
BasicObject
Kernel
Object
MyClass
SuperClass
………..
………..
method_missing
BasicObject
method_missing()
Kernel
Object
MyClass
SuperClass
………..
………..
method_missing
raise “Undefined method”
class RealCar
def drive
puts "vroom,vroom..."
end
end
class Client
attr_reader :age
def initialize(age)
@age = age
end
def drive(car)
car.drive
end
end
class ProxyCar
def initialize(real_car, driver_age)
@driver_age = driver_age
@real_car = real_car
end
def method_missing(name, *args)
car = get_real_car
check_access ? car.send(name, *args) :
puts("Sorry, can't do this")
end
def check_access
@driver_age > 16
end
def get_real_car
@real_car || (@real_car = Car.new
(@driver_age))
end
end
tom = Client.new(25)
car = RealCar.new()
proxy = ProxyCar.new(car, tom.age)
tom.drive(proxy)
vroom,vroom...
Perfection [in design] is
achieved, not when there is
nothing more to add, but when
there is nothing left to take
away.
- Antoine de Saint-Exupéry
CREDITS
Special thanks to all the people who made and released
these awesome resources for free:
▪ Busy Icons by Olly Holovchenko
▪ Presentation template by SlidesCarnival
▪ Photographs by Unsplash
GitHub Gists
▪ https://gist.github.
com/2809a0410ec452b64f4d
▪ https://gist.github.
com/d3638a2d15879806e679
▪ https://gist.github.
com/c1d7de9da194922305b2
ANY QUESTIONS?
You can find me at:
FredAtBootstrap
fred@bootstrap.me.uk

Design Patterns the Ruby way - ConFoo 2015

  • 1.
  • 2.
    Hello ConFoo! I AMFRED HEATH Developer, Problem solver, Ruby evangelist, Agile practitioner. You can find me at: @FredAtBootstrap bootstrap.me.uk
  • 3.
    Let’s start withthe first set of slides
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
    class PaymentStrategy def pay(sum) raise"can't call that!" end end class PayPalPayment < PaymentStrategy def pay(sum) puts ".....paying $#{sum} by PayPal" end end class WorldPayPayment < PaymentStrategy def pay(sum) puts ".....paying $#{sum} by WorldPay" end end class BitcoinPayment < PaymentStrategy def pay(sum) puts ".....paying $#{sum} by Bitcoin" end end class Purchase attr_reader :items, :sum attr_accessor :payment_method def initialize(items, payment_method) @payment_method = payment_method @sum = 0 items.each do |item, value| @sum += value end end def pay payment_method.pay(@sum) end end
  • 9.
    # class PaymentStrategy #def pay(sum) # raise "can't call that!" # end # end class PayPalPayment #< PaymentStrategy def pay(sum) puts ".....paying $#{sum} by PayPal" end end class WorldPayPayment #< PaymentStrategy def pay(sum) puts ".....paying $#{sum} by WorldPay" end end class BitcoinPayment #< PaymentStrategy def pay(sum) puts ".....paying $#{sum} by Bitcoin" end end class Purchase attr_reader :items, :sum attr_accessor :payment_method def initialize(items, payment_method) @payment_method = payment_method @sum = 0 items.each do |item, value| @sum += value end end def pay payment_method.pay(@sum) end end
  • 10.
    $> purchase =Purchase.new({cd_Wild_Beasts: 5.2, baseball_cap: 8.5 }, WorldPayPayment. new) $> purchase.pay .....paying $13.7 by WorldPay $> purchase = Purchase.new({cd_Wild_Beasts: 5.2, baseball_cap: 8.5 }, PayPalPayment. new) $> purchase.pay .....paying $13.7 by PayPal
  • 11.
    Lambda: an anonymous,first-class function.
  • 12.
    def m(&a_lambda) a_lambda.call end $> m{puts "this is a lambda"} "this is a lambda"
  • 13.
    def m(&a_lambda) x ='hello' a_lambda.call(x) end $> m {|x| puts "x is: #{x}"} "x is: hello"
  • 14.
    class PayPalPayment def pay(sum) puts".....paying $#{sum} by PayPal" end end class WorldPayPayment def pay(sum) puts ".....paying $#{sum} by WorldPay" end end class BitcoinPayment def pay(sum) puts ".....paying $#{sum} by Bitcoin" end end class Purchase attr_reader :items, :sum attr_accessor :payment_method def initialize(items, payment_method) @payment_method = payment_method @sum = 0 items.each do |item, value| @sum += value end end def pay payment_method.pay(@sum) end end
  • 15.
    #class PayPalPayment # defpay(sum) # puts ".....paying $#{sum} by PayPal" # end #end #class WorldPayPayment # def pay(sum) # puts ".....paying $#{sum} by WorldPay" # end #end #class BitcoinPayment # def pay(sum) # puts ".....paying $#{sum} by Bitcoin" # end #end class Purchase attr_reader :items, :sum attr_accessor :payment_method def initialize(items, payment_method) @payment_method = payment_method @sum = 0 items.each do |item, value| @sum += value end end def pay payment_method.pay(@sum) end end
  • 16.
    #class PayPalPayment # defpay(sum) # puts ".....paying $#{sum} by PayPal" # end #end #class WorldPayPayment # def pay(sum) # puts ".....paying $#{sum} by WorldPay" # end #end #class BitcoinPayment # def pay(sum) # puts ".....paying $#{sum} by Bitcoin" # end #end class Purchase attr_reader :items, :sum attr_accessor :payment_method def initialize(items, &payment_method) @payment_method = payment_method @sum = 0 items.each do |item, value| @sum += value end end def pay payment_method.call(@sum) end end
  • 17.
    $> purchase =Purchase.new({cd_Wild_Beasts: 5.2, baseball_cap: 8.5 }) {|sum| puts "..... paying $#{sum} by WorldPay"} $> purchase.pay .....paying $13.7 by WorldPay $> purchase = Purchase.new({cd_Wild_Beasts: 5.2, baseball_cap: 8.5 }) {|sum| puts "..... paying $#{sum} by Amazon Payments"} $> purchase.pay .....paying $13.7 by Amazon Payments
  • 18.
  • 19.
  • 20.
  • 21.
    APPLICATIONS ▪ User Interfaces ▪Queuing/Logging (Wizards) ▪ Do-Undo-Redo
  • 22.
  • 23.
    class Command def do_command raise"can't do this here" end def undo_command raise "can't do this here" end end class Incrementer < Command def initialize(aggregate) @aggregate = aggregate end def do_command @aggregate += 2 end def undo_command @aggregate -= 2 end end
  • 24.
    #class Command # defdo_command # raise "can't do this here" # end # def undo_command # raise "can't do this here" # end #end class Incrementer #< Command def initialize(aggregate) @aggregate = aggregate end def do_command @aggregate += 2 end def undo_command @aggregate -= 2 end end
  • 25.
    count = 0 commands= [] (1..10).each do |i| commands << Incrementer.new(count) end puts "Count initially is: #{count}" commands.each {|cmd| cmd.do_command} puts "Count after doing commands: #{count} Count initially is: 0 Count after doing commands: 0
  • 26.
    Closure: A first-classfunction that has lexical scope.
  • 27.
    outer = 1 defm a_var inner = 99 puts "inner var = #{inner}" proc {inner + a_var} end p = m(outer) puts "p is a #{p.class}" puts "result of proc call: #{p.call}" inner var = 99 p is a Proc result of proc call: 100
  • 28.
    outer = 1 defm a_var inner = 99 puts "inner var = #{inner}" proc {inner + a_var} end p = m(outer) puts "p is a #{p.class}" outer = 0 puts "changed outer to #{outer}" puts "result of proc call: #{p.call}" inner var = 99 p is a Proc changed outer to 0 result of proc call: 100
  • 29.
    class Command attr_accessor :cmd,:uncmd def initialize(do_command, undo_command) @cmd = do_command @uncmd = undo_command end def do_command @cmd.call end def undo_command @uncmd.call end end
  • 30.
    count = 0 commands= [] (1..10).each do |i| commands << Command.new(proc {count += 2}, proc {count -= 2}) end puts "Count initially is: #{count}" commands.each {|cmd| cmd.do_command} puts "Count after doing commands: #{count}" Count initially is: 0 Count after doing commands: 20
  • 31.
    count = 0 commands= [] (1..10).each do |i| commands << Command.new(proc {count += 2}, proc {count -= 2}) end puts "Count initially is: #{count}" commands.each {|cmd| cmd.do_command} puts "Count after doing commands: #{count}" commands.reverse_each {|cmd| cmd.undo_command} puts "Count after un-doing commands: #{count}" commands.each {|cmd| cmd.do_command} puts "Count after re-doing commands: #{count}" Count initially is: 0 Count after doing commands: 20 Count after un-doing commands: 0 Count after re-doing commands: 20
  • 32.
  • 33.
  • 34.
  • 35.
    APPLICATIONS ▪ Protection ▪ RemoteAccess ▪ Lazy Creation (Virtual Proxy)
  • 36.
  • 37.
    class Car def drive raise"use the Proxy instead" end end class RealCar < Car def drive puts "vroom,vroom..." end end class ProxyCar < Car def initialize(real_car, driver_age) @driver_age = driver_age @real_car = real_car end def check_access @driver_age > 16 end def get_real_car @real_car || (@real_car = Car.new (@driver_age)) end def drive car = get_real_car check_access ? car.drive : puts("Sorry, you're too young to drive") end end
  • 38.
    class RealCar def drive puts"vroom,vroom..." end end class ProxyCar def initialize(real_car, driver_age) @driver_age = driver_age @real_car = real_car end def check_access @driver_age > 16 end def get_real_car @real_car || (@real_car = Car.new (@driver_age)) end def drive car = get_real_car check_access ? car.drive : puts("Sorry, you're too young to drive") end end
  • 39.
    class RealCar def drive puts"vroom,vroom..." end end class Client attr_reader :age def initialize(age) @age = age end def drive(car) car.drive end end class ProxyCar def initialize(real_car, driver_age) @driver_age = driver_age @real_car = real_car end def check_access @driver_age > 16 end def get_real_car @real_car || (@real_car = Car.new (@driver_age)) end def drive car = get_real_car check_access ? car.drive : puts("Sorry, you're too young to drive") end end
  • 40.
    tom = Client.new(25) car= RealCar.new() proxy = ProxyCar.new(car, tom.age) tom.drive(proxy) vroom,vroom...
  • 41.
    tom = Client.new(15) car= RealCar.new() proxy = ProxyCar.new(car, tom.age) tom.drive(proxy) Sorry, you're too young to drive
  • 42.
    Dynamic Dispatching: selecting whichmethod to call at run-time
  • 43.
    puts [1, 2,3].reverse 3 2 1
  • 44.
    puts [1, 2,3].send(:reverse) 3 2 1
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
    class RealCar def drive puts"vroom,vroom..." end end class Client attr_reader :age def initialize(age) @age = age end def drive(car) car.drive end end class ProxyCar def initialize(real_car, driver_age) @driver_age = driver_age @real_car = real_car end def method_missing(name, *args) car = get_real_car check_access ? car.send(name, *args) : puts("Sorry, can't do this") end def check_access @driver_age > 16 end def get_real_car @real_car || (@real_car = Car.new (@driver_age)) end end
  • 53.
    tom = Client.new(25) car= RealCar.new() proxy = ProxyCar.new(car, tom.age) tom.drive(proxy) vroom,vroom...
  • 54.
    Perfection [in design]is achieved, not when there is nothing more to add, but when there is nothing left to take away. - Antoine de Saint-Exupéry
  • 55.
    CREDITS Special thanks toall the people who made and released these awesome resources for free: ▪ Busy Icons by Olly Holovchenko ▪ Presentation template by SlidesCarnival ▪ Photographs by Unsplash
  • 56.
    GitHub Gists ▪ https://gist.github. com/2809a0410ec452b64f4d ▪https://gist.github. com/d3638a2d15879806e679 ▪ https://gist.github. com/c1d7de9da194922305b2
  • 57.
    ANY QUESTIONS? You canfind me at: FredAtBootstrap fred@bootstrap.me.uk