冬のセール!
Strategy

Strategy を Ruby で

Strategy 振る舞いに関するデザインパターンの一つで 一連の振る舞いをオブジェクトに転換し 元のコンテキスト・オブジェクト内で交換可能とします

元のオブジェクトは コンテキストと呼ばれ 一つのストラテジー・オブジェクトへの参照を保持し それに振る舞いの実行を委任します コンテキストがその作業を実行する方法を変えるために 他のオブジェクトが 現在リンクされているオブジェクトを違うものと置き換えるかもしれません

複雑度

人気度

使用例 Strategy パターンは Ruby コードではよく見かけます 種々のフレームワークで クラスの拡張をせずに その振る舞いを変更できるようにするためによく使われます

見つけ方 入れ子になったオブジェクトに何か実際の作業をさせるメソッドや そのオブジェクトを他のものと入れ替えるための setter の存在で Strategy パターンを識別できます

概念的な例

この例は Strategy デザインパターンの構造を説明するためのものです 以下の質問に答えることを目的としています

  • どういうクラスからできているか
  • それぞれのクラスの役割は
  • パターンの要素同士はどう関係しているのか

main.rb: 概念的な例

# The Context defines the interface of interest to clients.
class Context
  # The Context maintains a reference to one of the Strategy objects. The
  # Context does not know the concrete class of a strategy. It should work with
  # all strategies via the Strategy interface.
  attr_writer :strategy

  # Usually, the Context accepts a strategy through the constructor, but also
  # provides a setter to change it at runtime.
  def initialize(strategy)
    @strategy = strategy
  end

  # Usually, the Context allows replacing a Strategy object at runtime.
  def strategy=(strategy)
    @strategy = strategy
  end

  # The Context delegates some work to the Strategy object instead of
  # implementing multiple versions of the algorithm on its own.
  def do_some_business_logic
    # ...

    puts 'Context: Sorting data using the strategy (not sure how it\'ll do it)'
    result = @strategy.do_algorithm(%w[a b c d e])
    print result.join(',')

    # ...
  end
end

# The Strategy interface declares operations common to all supported versions of
# some algorithm.
#
# The Context uses this interface to call the algorithm defined by Concrete
# Strategies.
class Strategy
  # @abstract
  #
  # @param [Array] data
  def do_algorithm(_data)
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

# Concrete Strategies implement the algorithm while following the base Strategy
# interface. The interface makes them interchangeable in the Context.

class ConcreteStrategyA < Strategy
  # @param [Array] data
  #
  # @return [Array]
  def do_algorithm(data)
    data.sort
  end
end

class ConcreteStrategyB < Strategy
  # @param [Array] data
  #
  # @return [Array]
  def do_algorithm(data)
    data.sort.reverse
  end
end

# The client code picks a concrete strategy and passes it to the context. The
# client should be aware of the differences between strategies in order to make
# the right choice.

context = Context.new(ConcreteStrategyA.new)
puts 'Client: Strategy is set to normal sorting.'
context.do_some_business_logic
puts "\n\n"

puts 'Client: Strategy is set to reverse sorting.'
context.strategy = ConcreteStrategyB.new
context.do_some_business_logic

output.txt: 実行結果

Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e

Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a

他言語での Strategy

Strategy を C# で Strategy を C++ で Strategy を Go で Strategy を Java で Strategy を PHP で Strategy を Python で Strategy を Rust で Strategy を Swift で Strategy を TypeScript で