Menu
Coddy logo textTech

ストラテジーパターン

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内に別の分岐を追加するのではなく、クラスを1つ追加します。

class Pickup
  def cost(_kg)
    0
  end
end

shipment.strategy = Pickup.new     # Shipment は変更されない

Rubyでは、ストラテジーはラムダであることがよくあります。callに応答できるものなら、ラムダ、メソッドオブジェクト、独自のクラスのオブジェクトのいずれであっても、保存して呼び出すことができます。

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

プログラムの実行中に1つのオブジェクトの振る舞いを変更する必要がある場合、または変化が1つのステップである場合は、サブクラスよりもストラテジーを選びます。サブクラスでは、オブジェクトの存続期間全体にわたって振る舞いが固定されます:

# サブクラス化: 作成時に固定
ExpressShipment.new(2)

# ストラテジー: 後から変更可能
shipment = Shipment.new(2, Standard.new)
shipment.strategy = Express.new
challenge icon

チャレンジ

簡単

配送戦略と ShippingCalculator を完成させてください。Standard#cost(kg) は 5 に 1 kg あたり 1.50 を加えた値、Express#cost(kg) は 15 に 1 kg あたり 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
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Rubyオンラインコンパイラ