概要
Observer Pattern 〜 CodeIQの出題を通知する
詳細
Observer Patternは、あるクラスの状態の変化を他のクラスに通知します。
通知する側をSubject, 通知を受ける側をObserverとよびます。
サンプル仕様
CodeIQの出題者と解答者を例にとります。
解答者はお気に入りの出題者の出題通知を受けることができます。
通知を受けると、解答者は問題に解答します。
サンプル修正前
出題者クラスの解答者を渡して、出題者クラス内で解答処理を呼び出す場合。
この内容だと本来、出題者クラスの責務ではない処理が混じっていて、
責務外の変更も受け持つ必要があって好ましくない。
require 'test_toolbox' module CodeIQ class Solver attr_reader :name def initialize(name) @name = name end def solve(examination_questions) name = examination_questions.name problem = examination_questions.problems.last puts "#{@name}は#{name}さんの#{problem}を解いて提出します。" end end class ExaminationQuestions attr_reader :name, :problems, :solvers def initialize(name, solvers) @problems = [] @name, @solvers = name, solvers end def publish_new_problem @problems << "新問題" + (@problems.size.succ).to_s @solvers.each { |solver|solver.solve(self) } end end end yuki = CodeIQ::ExaminationQuestions.new("結城浩", [CodeIQ::Solver.new('tbpgr'), CodeIQ::Solver.new("tanaka"), CodeIQ::Solver.new("sato")]) yuki.publish_new_problem
出力
tbpgrは結城浩さんの新問題1を解いて提出します。 tanakaは結城浩さんの新問題1を解いて提出します。 satoは結城浩さんの新問題1を解いて提出します。
サンプル修正後
Observerパターンを適用します。
RubyのObservableを利用します。
http://docs.ruby-lang.org/ja/2.0.0/class/Observable.html
拡張しやすくなったので要件を追加してObserverに
運営担当者と出題者も登録できるようにします。
運営担当は新たな出題があると宣伝をします。
出題者はは新たな出題があると参考にします。
require 'test_toolbox' require 'observer' module CodeIQ class Solver attr_reader :name def initialize(name) @name = name end def update(examination_questions) solve(examination_questions) end private def solve(examination_questions) name = examination_questions.name problem = examination_questions.last_problem puts "\t解答者の#{@name}は#{name}さんの#{problem}を解いて提出します。" end end class Management attr_reader :name def initialize(name) @name = name end def update(examination_questions) advertise(examination_questions) end private def advertise(examination_questions) name = examination_questions.name problem = examination_questions.last_problem puts "\t運営担当の#{@name}は#{name}さんの#{problem}を宣伝します。" end end class ExaminationQuestions include Observable attr_reader :name, :problems def initialize(name, observers = []) @problems = [] @name = name observers.each { |observer|add_observer observer } end def publish_new_problem @problems << "新問題" + (@problems.size.succ).to_s puts "#{@name}は#{last_problem}を出題しました。" changed notify_observers self changed false end def last_problem @problems.last end def update(examination_questions) to_use_as_reference(examination_questions) end private def to_use_as_reference(examination_questions) name = examination_questions.name problem = examination_questions.last_problem puts "\t出題者の#{@name}は#{name}さんの#{problem}を参考にします。" end end end suzuki = CodeIQ::Solver.new('suzuki') yuki = CodeIQ::ExaminationQuestions.new("結城浩", [suzuki]) # 結城先生の問題購読者が suzuki 1人の状態で新問題が出題されます。 yuki.publish_new_problem dp_line __LINE__ # 結城先生の問題購読者に 解答者「tanaka 、 sato」 運営担当「jessicaCIQ」 出題者「tbpgr」 を追加します。 additional_observers = [CodeIQ::Solver.new("tanaka"), CodeIQ::Solver.new("sato"), CodeIQ::Management.new("jessicaCIQ"), CodeIQ::ExaminationQuestions.new("tbpgr")] # 結城先生の問題購読者が tbpgr/tanaka/sato の3人の状態で新問題が出題されます。 additional_observers.each { |additional_observer|yuki.add_observer(additional_observer) } yuki.publish_new_problem dp_line __LINE__ # 結城先生の問題購読者から suzuki を削除します。 yuki.delete_observer suzuki # 結城先生の問題購読者が tanaka/sato の2人の状態で新問題が出題されます。 yuki.publish_new_problem
出力
結城浩は新問題1を出題しました。 解答者のsuzukiは結城浩さんの新問題1を解いて提出します。 --------------------|filename=|line=80|-------------------- 結城浩は新問題2を出題しました。 解答者のsuzukiは結城浩さんの新問題2を解いて提出します。 解答者のtanakaは結城浩さんの新問題2を解いて提出します。 解答者のsatoは結城浩さんの新問題2を解いて提出します。 運営担当のjessicaCIQは結城浩さんの新問題2を宣伝します。 出題者のtbpgrは結城浩さんの新問題2を参考にします。 --------------------|filename=|line=88|-------------------- 結城浩は新問題3を出題しました。 解答者のtanakaは結城浩さんの新問題3を解いて提出します。 解答者のsatoは結城浩さんの新問題3を解いて提出します。 運営担当のjessicaCIQは結城浩さんの新問題3を宣伝します。 出題者のtbpgrは結城浩さんの新問題3を参考にします。