明日の読書会の担当なのに資料作ってない。
\(^o^)/オワタ
そうも言ってられないので作れるところまで作る。
背景
Template Methodは継承で成り立ってる → 親クラスに依存してしまう
Templateを決定したあとに方法を変更 → 全体を書き換える必要がある
委譲で回避
- 継承より委譲
- Templateの種類を変えるたびにサブクラスを作るのをやめる
- TemplateをMethodからクラスに変えてしまう
class Formatter def output report(title, text) # RubyにはAbstractクラスは無いがエラーを返す仕組みにして対応 raise 'Abstract method called' end end class HTMLFormatter < Formatter def output report(title, text) # HTMLを出力するコードを書く puts('<html>') end end class PlainTextFormatter < Formatter def output report(title, text) # PlainTextを出力するコードを書く puts("**** #{title} ****") end end
出力フォーマットをReportクラスから取り除いたのでP69のReportクラスと比べてシンプルに
class Report attar_reader :title, :text attr_accesseor :formatter def initialize(formatter) @title = "タイトル" @text = ["Sample1", "Sample2"] @formatter = formatter end def output_report @formatter.output_report(@title, @text) end end
Reportクラスを使用する場合は利用したいFormatterを
Reportクラスのコンストラクタ引数に入れる。
# Report.new(利用したいFormatter.new) report = Report.new(HTMLFormatter.new) report.output_report
アルゴリズムを別のオブジェクトとして扱う → Strategy(同じ目的を持った一群のオブジェクト)
Strategyは同一のインターフェイスを持つ → 利用者は親となるStrategyを知っていればいい
Strategyのメリット
- 処理の切り出しが行える → 変更や保守が容易に
- 委譲と集約に基づく → 継承より実行時の切り替えが楽
- Template Methodと同様に利用変更の切り替え部分が数箇所に集中
- Template Method:サブクラスの選択時に決定
- Strategy:実行時に選択して決定
- 利用者とStrategyが別クラス → ○データ分離が行える ×データのやり取りが手間
×の解決方法としてStrategyに利用者を渡してしまう
class Report attar_reader :title, :text attr_accesseor :formatter def initialize(formatter) @title = "タイトル" @text = ["Sample1", "Sample2"] @formatter = formatter end def output_report @formatter.output_report(self) end end class HTMLFormatter < Formatter def output report(context) # HTMLを出力するコードを書く puts('<html>') # title => context.title, text => context.textでアクセスするように変更 end end
Rubyっぽく変更
同じインターフェイスを持って同じような動きをする = ダックタイピング
ダックタイピングの考え方からするとFormaterクラスはらないので削除
class HTMLFormatter < Formatter def output report(title, text) # HTMLを出力するコードを書く puts('<html>') # title => context.title, text => context.textでアクセスするように変更 end end class PlainTextFormatter < Formatter def output report(title, text) # PlainTextを出力するコードを書く puts("**** #{title} ****") # title => context.title, text => context.textでアクセスするように変更 end end class Report attar_reader :title, :text attr_accesseor :formatter def initialize(formatter) @title = "タイトル" @text = ["Sample1", "Sample2"] @formatter = formatter end def output_report @formatter.output_report(self) end end
Procを使ってもっと簡単に
Procオブジェクト = コードの塊
生成にはlambdaかProc.newを使う
呼び出しにはProc#callか[]を使う。
hoge = lambda{puts "hoge"} hoge.call # => hoge hoge[] #=> hoge huga = Proc.new{puts "huga"} huga.call # => huga huga[] # => huga #引数あり hoge = lambda{|x,y| x+y} hoge.call(1, 2) # => 3 hoge[1, 2] #=> 3 huga = Proc.new{|x,y| x+y} huga.call(1, 2) # => 3 huga[1, 2] # => 3
Procオブジェクトをメソッドの引数に渡す
def sample_method puts "ここからブロックを実行" yield puts "ここまでブロックを実行" end sample_method do puts "ブロックが実行されています" end # => "ここからブロックを実行" "ブロックが実行されています" "ここまでブロックを実行" # => こっちの方がすっきり sample_method{puts "ブロックが実行されています"}
yieldに引数を設定しておくことも可能
def sample_method puts "ここからブロックを実行" yield("現在") puts "ここまでブロックを実行" end sample_method do |str| puts "#{str}ブロックが実行されています" end # => "ここからブロックを実行" "現在ブロックが実行されています" "ここまでブロックを実行" # => こっちの方がすっきり sample_method{|str|puts "#{str}ブロックが実行されています"}
ブロックに名前をつけて実行することも可能
# 関数の引数リストの最後に&をつけることで実現可能 def sample_method(&block) block.call("hoge") end sample{|x|puts x} # => hoge def sample_method(&block) block["hoge"] end sample{|x|puts x} # => hoge
Procオブジェクトを利用したStrategy
- Procオブジェクトを使うことでクラスを作る必要が無くなる
- 何も無いところからStrategyが作れるようになる!
- ただし、1つのメソッドで足りるときだけ有効
- やることがいっぱいあるならクラスベースの方がいい
class Report attar_reader :title, :text attr_accesseor :formatter def initialize(&formatter) # &で @title = "タイトル" @text = ["Sample1", "Sample2"] @formatter = formatter end def output_report @formatter.call(self) end end HTML_FORMATTER = lambda do |context| #HTMLフォーマットで出力をする処理を記述 end # Procオブジェクトを引数に渡すときは&が必要 report = Report.new &HTML_FORMMATER