概要
Rubyによるデザインパターン第4章。
Strategy Pattern。
Rubyによるデザインパターン5原則に則って理解する。
どんなパターンか
抽象的な処理と具体的な処理を分離することで、
変化に強い構造を実現する。
→その目的はTemplate Methodとほぼ同等
Template Methodの課題
しかし、Template Methodには欠点がある。それは、
継承をベースにしていること。
継承の欠点
http://qiita.com/kidachi_/items/4b63de9ad5a97726c50c#2-3
- スーパークラスの振る舞いの変更は、サブクラスの振る舞いを変える
- サブクラスはスーパークラスの中身を覗くことが出来る
そこでStrategyでは、
変化しやすいコードの塊を抽出し、
全く別のクラスに閉じ込める。
つまり、集約を使う。
異なるオブジェクトからアルゴリズムを引き出す
同じ目的を持った一群のオブジェクトをストラテジ(群)と捉える。
ストラテジ群は、そのすべてが全く同じインターフェイスを提供する。
コンテキスト(ストラテジを利用する側)は、各ストラテジを
取り替え可能パーツとして扱うことが出来るようになる。
→関心の分離 を実現する。
実装
コンテキスト(ストラテジを利用する側)
class Report
attr_reader :title, :text
attr_accessor :formatter
def initialize(formatter)
@title = '月次報告'
@text = ['最高!', '順調', '普通']
@formatter = formatter
end
def output_report
@formatter.output_report(self)
end
end
ストラテジ1(フォーマット:HTML)
class HTMLFormatter
def output_report(context)
puts('<html>')
puts(' <head>')
puts(" <title>#{context.title}</title>")
puts(' </head>')
puts(' <body>')
context.text.each do |line|
puts(" <p>#{line}</p>")
end
puts(' </body>')
puts('</html>')
end
end
ストラテジ2(フォーマット:PlainText)
class PlainTextFormatter
def output_report(context)
puts("*** #{context.title} ***")
context.text.each do |line|
puts(line)
end
end
end
ストラテジの活用
[9] pry(main)> report = Report.new(HTMLFormatter.new)
[10] pry(main)> report.output_report
<html>
<head>
<title>月次報告</title>
</head>
<body>
<p>最高!</p>
<p>順調</p>
<p>普通</p>
</body>
</html>
[12] pry(main)> report.formatter = PlainTextFormatter.new
[13] pry(main)> report.output_report
*** 月次報告 ***
最高!
順調
普通
Template MethodにしてもStrategyにしても、
アルゴリズムの切り替えを1箇所(から多くても数箇所)に集中できる。
コンテキストとストラテジ間でのデータ共有
2者のデータ共有の方法としては2パターン存在する。
- ストラテジへ、コンテキスト自体の参照(self)を渡す
- ストラテジ側で使う必要のあるコンテキスト側のデータは、全て引数として渡す
上の例では、1を用いているが、2のパターンも見てみる。
ストラテジ側で使う必要のあるコンテキスト側のデータは、全て引数として渡す
class Report
attr_reader :title, :text
attr_accessor :formatter
def initialize(formatter)
@title = '月次報告'
@text = ['最高!', '順調', '普通']
@formatter = formatter
end
def output_report
@formatter.output_report(@title, @text) # (オブジェクトではなく)値を引数として渡す
end
end
class PlainTextFormatter
def output_report(title, text) # (オブジェクトではなく)値を受け取る
puts("*** #{title} ***")
text.each do |line|
puts(line)
end
end
end
[3] pry(main)> report = Report.new(PlainTextFormatter.new)
[4] pry(main)> report.output_report
*** 月次報告 ***
最高!
順調
普通
メリット・デメリット
1. ストラテジへ、コンテキスト自体の参照(self)を渡す
メリット:データの流れがシンプルになる。
デメリット:コンテキストとストラテジの結合度が上がってしまう。
2. ストラテジ側で使う必要のあるコンテキスト側のデータは、全て引数として渡す
メリット:コンテキストとストラテジの分離は保てる
デメリット:渡すデータの数によっては、複雑で大量になる可能性がある。
→状況に応じて適切なものを利用すること。
Procやブロックでストラテジを作る
ここまで、コンテキストとストラテジの関係を、
「オブジェクトとオブジェクト(の分離)」として捉えてきたが、
オブジェクトだけでなく、「コード」レベルでも、
分離し、持ち回すことが出来ないだろうか?
→ Proc/ブロックで実現する。
※ Procやブロックの基礎は以下を参照。
[Ruby基礎] ブロックとProcをちゃんと理解する
http://qiita.com/kidachi_/items/15cfee9ec66804c3afd2
コンテキスト2(ストラテジを利用する側)
コンテキスト初期化時に渡されてくるストラテジは、(クラスではなく)ブロックを期待する。
class Report
attr_reader :title, :text
attr_accessor :formatter
def initialize(&formatter) # 渡されてくるブロックをproc化
@title = '月次報告'
@text = ['最高!', '順調', '普通']
@formatter = formatter
end
def output_report
@formatter.call(self) # Procをcall
end
end
ストラテジ3(フォーマット:PlainText、タイプ:ブロック)
report = Report.new do |context|
puts("*** #{context.title} ***")
context.text.each do |line|
puts(line)
end
end
[11] pry(main)> report.output_report
*** 月次報告 ***
最高!
順調
普通
ストラテジ4(フォーマット:PlainText、タイプ:Proc)
PLAIN_TEXT_FORMATTER = lambda { |context|
puts("*** #{context.title} ***")
context.text.each do |line|
puts(line)
end
}
report = Report.new(&PLAIN_TEXT_FORMATTER) # procをブロック化して渡す
[15] pry(main)> report.output_report
*** 月次報告 ***
最高!
順調
普通
procやブロックを活用することで、手軽にストラテジを作ることが出来た。
まとめ
Strategyパターンにより、
- 変わるもの(ストラテジ)と変わらないもの(コンテキスト)を分離して、変化に強い構造へ
- 継承より集約を実現
- ブロックによるストラテジも可能
以下へ続く
【Observer】-本日のニュースをお届けします-
http://qiita.com/kidachi_/items/ce18d2a926c558159689