Decorator Pattern
Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 47 of 57.
The decorator pattern wraps an object in another object with the same interface, which adds something before or after passing the call on. Decorators can be stacked in any order:
class Coffee
def cost
2.0
end
def description
"coffee"
end
end
class Milk
def initialize(drink)
@drink = drink
end
def cost
@drink.cost + 0.5
end
def description
"#{@drink.description}, milk"
end
end
order = Milk.new(Milk.new(Coffee.new))
puts format("%s: %.2f", order.description, order.cost)Output:
coffee, milk, milk: 3.00Every decorator stores the wrapped object and forwards to it, so a shared base class can hold that part. Each concrete decorator then overrides only what it changes:
class Coffee
def cost
2.0
end
def description
"coffee"
end
end
class Extra
def initialize(drink)
@drink = drink
end
def cost
@drink.cost
end
def description
@drink.description
end
end
class Sugar < Extra
def cost
super + 0.2
end
def description
super + ", sugar"
end
end
order = Sugar.new(Coffee.new)
puts format("%s: %.2f", order.description, order.cost)Output:
coffee, sugar: 2.20The standard library's SimpleDelegator (require 'delegate') forwards every method it does not define to the wrapped object, which keeps decorators short when the interface is large:
require 'delegate'
class Loud < SimpleDelegator
def to_s
super.upcase + "!"
end
end
text = Loud.new("hello")
puts text
puts text.size
puts text.start_with?("he")Output:
HELLO!
5
trueDecorators add behaviour to single objects at run time and can be combined freely. With subclasses, every combination such as MilkSugarCoffee would need a class of its own:
Sugar.new(Milk.new(Coffee.new)) # any combination, any order
Whip.new(Sugar.new(Coffee.new))
# with subclasses: MilkCoffee, SugarCoffee, MilkSugarCoffee, ...Challenge
EasyComplete the coffee decorators. Coffee costs 2.00 and is described as coffee. Milk adds 0.50 and , milk, Sugar adds 0.20 and , sugar, and Whip adds 0.70 and , whip. The three decorators share the base class Extra, which stores the wrapped drink and forwards cost and description; each subclass uses super.
Each input line is an order such as milk sugar. The supplied code wraps a new Coffee in the decorators in that order and prints coffee, milk, sugar: 2.70.
Your code goes in coffee.rb, extra.rb, milk.rb, sugar.rb and whip.rb. main.rb holds the supplied input/output code and cannot be edited.
Try it yourself
require_relative 'coffee'
require_relative 'extra'
require_relative 'milk'
require_relative 'sugar'
require_relative 'whip'
# Supplied input/output code: keep it as it is
input = []
while (line = gets)
input << line.chomp
end
extras = { "milk" => Milk, "sugar" => Sugar, "whip" => Whip }
input.each do |line|
drink = line.split.reduce(Coffee.new) { |d, name| extras[name].new(d) }
puts format("%s: %.2f", drink.description, drink.cost)
end
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesInstance VariablesMethods and selfto_s and inspectRecap - Library Card7Polymorphism and Duck Typing
PolymorphismDuck TypingAbstract Methodscase and ClassesRecap - Payment Methods2Accessors and Self
Readers and WritersCustom Settersself in MethodsIdentity and EqualityRecap - Contact List8Special Methods
Equality and hashOperator MethodsIndexing and AppendingCustom CollectionsRecap - Fractions11Project: Library Management
Books and MembersBorrowing Books3Class Methods and Variables
Class MethodsClass Instance VariablesConstantsFactory MethodsRecap - Coupon CodesPractice on your own: Online Ruby compiler