Ruby
programming
デザインパターン

Rubyによるデザインパターン【Strategy】-取り替え可能パーツ群を戦略的に利用せよ-

More than 3 years have passed since last update.


概要

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パターン存在する。


  1. ストラテジへ、コンテキスト自体の参照(self)を渡す

  2. ストラテジ側で使う必要のあるコンテキスト側のデータは、全て引数として渡す

上の例では、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