Strategy Pattern
Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 45 of 57.
The strategy pattern puts one part of an algorithm into its own object and lets the main object delegate to it. Swapping the strategy changes the behaviour without changing the main class:
class Standard
def cost(kg)
5 + kg * 1.5
end
end
class Express
def cost(kg)
15 + kg * 3
end
end
class Shipment
attr_writer :strategy
def initialize(kg, strategy)
@kg = kg
@strategy = strategy
end
def cost
@strategy.cost(@kg)
end
end
s = Shipment.new(2, Standard.new)
puts s.cost
s.strategy = Express.new
puts s.costOutput:
8.0
21The strategies share only a method name, so any object with cost works: duck typing again. Adding a new way to ship means adding one class, not another branch inside Shipment:
class Pickup
def cost(_kg)
0
end
end
shipment.strategy = Pickup.new # Shipment is unchangedIn Ruby a strategy is often a lambda. Anything that responds to call can be stored and invoked, whether it is a lambda, a method object or an object of your own class:
class TextFormatter
def initialize(strategy)
@strategy = strategy
end
def format_all(words)
words.map { |w| @strategy.call(w) }.join(" ")
end
end
class Censor
def call(word)
word.size > 3 ? "*" * word.size : word
end
end
words = %w[ruby is great]
puts TextFormatter.new(->(w) { w.upcase }).format_all(words)
puts TextFormatter.new(:capitalize.to_proc).format_all(words)
puts TextFormatter.new(Censor.new).format_all(words)Output:
RUBY IS GREAT
Ruby Is Great
**** is *****Choose strategy over subclasses when one object must change its behaviour while the program runs, or when the variation is a single step. A subclass fixes the behaviour for the object's whole life:
# subclassing: fixed at creation
ExpressShipment.new(2)
# strategy: can change later
shipment = Shipment.new(2, Standard.new)
shipment.strategy = Express.newChallenge
EasyComplete the shipping strategies and ShippingCalculator. Standard#cost(kg) is 5 plus 1.50 per kg, Express#cost(kg) is 15 plus 3 per kg, and Pickup#cost(kg) is 0. The calculator is created with a strategy, can change it with strategy=, and quote(kg) returns 2.5 kg by Express: 22.50, using the strategy's class name.
The supplied code starts with Standard and reads commands use express, use pickup, use standard or quote 2.5, printing every quote.
Your code goes in standard.rb, express.rb, pickup.rb and shipping_calculator.rb. main.rb holds the supplied input/output code and cannot be edited.
Try it yourself
require_relative 'standard'
require_relative 'express'
require_relative 'pickup'
require_relative 'shipping_calculator'
# Supplied input/output code: keep it as it is
input = []
while (line = gets)
input << line.chomp
end
strategies = { "standard" => Standard, "express" => Express, "pickup" => Pickup }
calc = ShippingCalculator.new(Standard.new)
input.each do |line|
cmd, arg = line.split
if cmd == "use"
calc.strategy = strategies[arg].new
else
puts calc.quote(arg.to_f)
end
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