GoFのデザインパターン(Design Pattern)のストラテジー(Strategy)のRubyコードを使った紹介記事です。
ストラテジーパターンは、たとえば5ステップの中の3ステップが異なったAとBがあり、このAとBをスイッチしたい時に使えるパターンです。
🐰 ストラテジの構成
ストラテジは次の3つのオブジェクトによって構成されます。
- コンテキスト(Context):ストラテジの利用者
- 抽象戦略(Strategy):同じ目的をもった一連のオブジェクトを抽象化したもの
- 具象戦略(ConcreteStrategy):具体的なアルゴリズム
ストラテジのアイデアは、コンテキストが「委譲」によってアルゴリズムを交換できるようにすることです。委譲とは、ある機能をもつオブジェクトを生成してオブジェクトに処理を依頼することです。
🍮 ストラテジのメリット
- 使用するアルゴリズムに多様性を持たせることができる
- コンテキストと戦略を分離することでデータも分離できる
- 継承よりもストラテジを切り替えるのが楽
😼 サンプルソース1
レポートをHTML形式とプレーンテキスト形式で作成するプログラムをサンプルとしてストラテジーパターンを解説します。サンプルの概要は次のとおりです。
- Report(コンテキスト):レポートを表す
- Formatter(抽象戦略):レポートの出力を抽象化したクラス
- HTMLFormatter(具象戦略1):HTMLフォーマットでレポートを出力
- PlaneTextFormatter(具象戦略2):PlanTextフォーマットでレポートを出力
まずイメージしやすい、HTML形式で出力するHTMLFormatter
クラスとPlaneTextFormatter
クラス、そしてその2つのクラスのインタフェースを規定するFormatterクラスを作成します。
# レポートの出力を抽象化したクラス(抽象戦略) |
end
end
# PlaneText形式(*****で囲う)に整形して出力(具体戦略)
class PlaneTextFormatter < Formatter
def output_report(report)
puts "***** #{report.title} *****"
report.text.each { |line| puts(line) }
end
end
続いてレポートを表すReportクラスを作成します。このクラスにはformatter
があり、このformatter
によって出力フォーマットを設定します。
# レポートを表す(コンテキスト) |
コーディングは以上です。では結果を確認します。
report = Report.new(HTMLFormatter.new) |
Reportクラス内のformatter
がレポートの出力を委譲されています。
上の結果からformatter
をスイッチすれば出力形式(戦略)を変更させることができるのを確認できました。
ちなみに、ここにあるFormatter
クラスはインタフェースを規定するだけのクラスですので、Rubyらしく書くなら不要です。(ダック・タイピング哲学)
🏀 サンプルソース2
先ほどのソースコードをProcオブジェクト(コードブロック)を使って置き換えると次のようになります。
Procオブジェクトは、コードの塊を保持するオブジェクトです。lambda
が良く使われます
class Report |
コーディングは以上です。では結果を確認します。
report = Report.new(&HTML_FORMATTER) |
先ほどよりもRubyらしいコードで同様の結果を得ることができました。
🎉 ストラテジの注意点
- コンテキストとストラテジ間のインターフェイスがストラテジの種類の増加を妨げないようにする
- コンテキストの変更がストラテジに影響を与えないようにする
コンテキストからストラテジへのデータの渡し方は、(1) ストラテジメソッドを呼び出すときに適切なデータを渡す
(2) コンテキストへの参照をストラテジに渡すといった方法があります。これを適切に選択してストラテジの種類を増やすことを阻害しないようにしてください。