Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

Ruby | Observer Pattern 〜 CodeIQの出題を通知する

概要

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を参考にします。