Tamás Michelberger
Building software even when it's an overkill
[A decorator] allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.
source: Wikipedia
class Coffee
def will_keep_you_rolling_for
2.hours
end
end
class ExtraLarge
def initialize(coffee) @coffee = coffee end
def will_keep_you_rolling_for
@coffee.will_keep_you_rolling_for + 2.hours
end
end
class SuperStrong
def initialize(coffee) @coffee = coffee end
def will_keep_you_rolling_for
@coffee.will_keep_you_rolling_for * 2
end
end
SuperStrong.new(ExtraLarge.new(Coffee.new)).will_keep_you_rolling_for
#=> 8
Usually only a few methods are added and/or modified in a decorator. What to do with the other methods?
Class.new(ActiveRecord::Base).instance_methods.size
=> 386
Delegating every method by hand is almost impossible.
Person = Struct.new(:first_name, :last_name)
class PersonWithFullName
extend Forwardable
def initialize(person)
@person = person
end
def_delegators :@person, :first_name, :last_name
def full_name
"#{first_name} #{last_name}"
end
end
Person = Struct.new(:first_name, :last_name)
class PersonWithScrambledName < SimpleDelegator
def scrambled_name
"#{scramble(first_name)} #{scramble(last_name)}"
end
private
def scramble(str)
str.chars.shuffle.join
end
end
person_decorator = PersonWithScrambledName.new(Person.new('John', 'Doe'))
person_decorator.scrambled_name #=> "onhJ Doe"
person_decorator.__getobj__ #=> #<struct Person first_name="John", last_name="Doe">
Person = Struct.new(:first_name, :last_name)
class SayHiForMe < DelegateClass(Person)
def say_hi
"Hi, I'm #{first_name}"
end
end
SayHiForMe.new(Person.new('John', 'Doe')).say_hi
=> "Hi, I'm John"
SayHiForMe.public_instance_methods
=> [:say_hi, :__getobj__, :__setobj__, :first_name, :first_name=,
:last_name, :last_name=, ...]
By Tamás Michelberger
Decorator pattern. Presented at budapest.rb 2015/02