策略模式
本课属于 Coddy Ruby 学习旅程的“面向对象编程”单元。第 45 课,共 57 课。
策略模式会将算法的一部分放入独立的对象中,并让主对象委托给它。切换策略可以改变行为,而无需更改主类:
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.cost输出:
8.0
21这些策略只有方法名相同,因此任何具有 cost 的对象都能使用:这又是鸭子类型。添加一种新的运输方式意味着添加一个类,而不是在 Shipment 中再添加一个分支:
class Pickup
def cost(_kg)
0
end
end
shipment.strategy = Pickup.new # Shipment 保持不变在 Ruby 中,策略通常是一个 lambda。任何响应 call 的对象都可以被存储和调用,无论它是 lambda、方法对象,还是你自己定义的类的对象:
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)输出:
RUBY IS GREAT
Ruby Is Great
**** is *****当一个对象必须在程序运行时改变其行为,或者变化只涉及一个步骤时,优先选择策略而不是子类。子类会在对象的整个生命周期内固定其行为:
# 通过子类实现:创建时就确定了
ExpressShipment.new(2)
# 使用策略:之后还可以更改
shipment = Shipment.new(2, Standard.new)
shipment.strategy = Express.new挑战
简单完成运输策略和 ShippingCalculator。Standard#cost(kg) 的费用为 5 加上每公斤 1.50,Express#cost(kg) 的费用为 15 加上每公斤 3,Pickup#cost(kg) 的费用为 0。创建计算器时需要传入一个策略,也可以用 strategy= 更改策略。quote(kg) 会返回 2.5 kg by Express: 22.50,其中使用策略的类名。
提供的代码从 Standard 策略开始,并读取 use express、use pickup、use standard 或 quote 2.5 等命令,然后打印每条报价。
你的代码应写在 standard.rb、express.rb、pickup.rb 和 shipping_calculator.rb 中。main.rb 包含提供的输入/输出代码,不能编辑。
亲手试试
require_relative 'standard'
require_relative 'express'
require_relative 'pickup'
require_relative 'shipping_calculator'
# 提供的输入输出代码:保持原样
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
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Ruby 在线编译器