design patterns the ruby way - confoo 2015

57
DESIGN PATTERNS THE WAY

Upload: fred-heath

Post on 16-Jul-2015

270 views

Category:

Software


1 download

TRANSCRIPT

Page 1: Design Patterns the Ruby way - ConFoo 2015

DESIGN PATTERNS

THE WAY

Page 2: Design Patterns the Ruby way - ConFoo 2015

Hello ConFoo!I AM FRED HEATH

Developer, Problem

solver, Ruby evangelist,

Agile practitioner.

You can find me at:

@FredAtBootstrap

bootstrap.me.uk

Page 3: Design Patterns the Ruby way - ConFoo 2015

Let’s start with the

first set of slides

Page 4: Design Patterns the Ruby way - ConFoo 2015

The

gospel

according

to Russ

Page 5: Design Patterns the Ruby way - ConFoo 2015

1STRATEGY

Page 6: Design Patterns the Ruby way - ConFoo 2015

APPLICATIONS

▪ Dynamically choose algorithm

Page 7: Design Patterns the Ruby way - ConFoo 2015

Strategy

Page 8: Design Patterns the Ruby way - ConFoo 2015

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

Page 9: Design Patterns the Ruby way - ConFoo 2015

# 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

Page 10: Design Patterns the Ruby way - ConFoo 2015

$> 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

Page 11: Design Patterns the Ruby way - ConFoo 2015

Lambda: an anonymous, first-class

function.

Page 12: Design Patterns the Ruby way - ConFoo 2015

def m(&a_lambda)

a_lambda.call

end

$> m {puts "this is a lambda"}

"this is a lambda"

Page 13: Design Patterns the Ruby way - ConFoo 2015

def m(&a_lambda)

x = 'hello'

a_lambda.call(x)

end

$> m {|x| puts "x is: #{x}"}

"x is: hello"

Page 14: Design Patterns the Ruby way - ConFoo 2015

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

Page 15: Design Patterns the Ruby way - ConFoo 2015

#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

Page 16: Design Patterns the Ruby way - ConFoo 2015

#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

Page 17: Design Patterns the Ruby way - ConFoo 2015

$> 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

Page 18: Design Patterns the Ruby way - ConFoo 2015

From:

Page 19: Design Patterns the Ruby way - ConFoo 2015

To:

Page 20: Design Patterns the Ruby way - ConFoo 2015

2COMMAND

Page 21: Design Patterns the Ruby way - ConFoo 2015

APPLICATIONS

▪ User Interfaces

▪ Queuing/Logging (Wizards)

▪ Do-Undo-Redo

Page 22: Design Patterns the Ruby way - ConFoo 2015

Command

Page 23: Design Patterns the Ruby way - ConFoo 2015

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

Page 24: Design Patterns the Ruby way - ConFoo 2015

#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

Page 25: Design Patterns the Ruby way - ConFoo 2015

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

Page 26: Design Patterns the Ruby way - ConFoo 2015

Closure: A first-class function

that has lexical scope.

Page 27: Design Patterns the Ruby way - ConFoo 2015

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

Page 28: Design Patterns the Ruby way - ConFoo 2015

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

Page 29: Design Patterns the Ruby way - ConFoo 2015

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

Page 30: Design Patterns the Ruby way - ConFoo 2015

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

Page 31: Design Patterns the Ruby way - ConFoo 2015

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

Page 32: Design Patterns the Ruby way - ConFoo 2015

From:

Page 33: Design Patterns the Ruby way - ConFoo 2015

To:

Page 34: Design Patterns the Ruby way - ConFoo 2015

3PROXY

Page 35: Design Patterns the Ruby way - ConFoo 2015

APPLICATIONS

▪ Protection

▪ Remote Access

▪ Lazy Creation (Virtual Proxy)

Page 36: Design Patterns the Ruby way - ConFoo 2015

Proxy

Page 37: Design Patterns the Ruby way - ConFoo 2015

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

Page 38: Design Patterns the Ruby way - ConFoo 2015

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

Page 39: Design Patterns the Ruby way - ConFoo 2015

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

Page 40: Design Patterns the Ruby way - ConFoo 2015

tom = Client.new(25)

car = RealCar.new()

proxy = ProxyCar.new(car, tom.age)

tom.drive(proxy)

vroom,vroom...

Page 41: Design Patterns the Ruby way - ConFoo 2015

tom = Client.new(15)

car = RealCar.new()

proxy = ProxyCar.new(car, tom.age)

tom.drive(proxy)

Sorry, you're too young to drive

Page 42: Design Patterns the Ruby way - ConFoo 2015

Dynamic Dispatching: selecting

which method to call at run-time

Page 43: Design Patterns the Ruby way - ConFoo 2015

puts [1, 2, 3].reverse

3

2

1

Page 44: Design Patterns the Ruby way - ConFoo 2015

puts [1, 2, 3].send(:reverse)

3

2

1

Page 45: Design Patterns the Ruby way - ConFoo 2015

BasicObject

Kernel

Object

MyClass

SuperClass

………..………..

a_method

Page 46: Design Patterns the Ruby way - ConFoo 2015

BasicObject

Kernel

Object

MyClass

SuperClass

………..………..

a_method

Page 47: Design Patterns the Ruby way - ConFoo 2015

BasicObject

Kernel

Object

MyClass

SuperClass

………..………..

a_method

Page 48: Design Patterns the Ruby way - ConFoo 2015

BasicObject

Kernel

Object

MyClass

SuperClass

………..………..

a_method

Page 49: Design Patterns the Ruby way - ConFoo 2015

BasicObject

Kernel

Object

MyClass

SuperClass

………..………..

a_method

Page 50: Design Patterns the Ruby way - ConFoo 2015

BasicObject

Kernel

Object

MyClass

SuperClass

………..………..

method_missing

Page 51: Design Patterns the Ruby way - ConFoo 2015

BasicObject

method_missing()

Kernel

Object

MyClass

SuperClass

………..………..

method_missingraise “Undefined method”

Page 52: Design Patterns the Ruby way - ConFoo 2015

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

Page 53: Design Patterns the Ruby way - ConFoo 2015

tom = Client.new(25)

car = RealCar.new()

proxy = ProxyCar.new(car, tom.age)

tom.drive(proxy)

vroom,vroom...

Page 54: Design Patterns the Ruby way - ConFoo 2015

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

Page 55: Design Patterns the Ruby way - ConFoo 2015

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

Page 57: Design Patterns the Ruby way - ConFoo 2015

ANY QUESTIONS?

You can find me at:

FredAtBootstrap

[email protected]