design patterns the ruby way - confoo 2015
TRANSCRIPT
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
1STRATEGY
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:
2COMMAND
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:
3PROXY
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_missingraise “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