Mediatorパターン(GoF)

参考書籍一覧(Amazon アソシエイトリンク)

インスタンス同士で協調して処理しなければならないが、インスタンス同士で依存関係を持ちたくない場合に使うパターン。

パート従業員のシフト管理を行うシステムを考える。常に2人の勤務でシフトを組まなければならないとする。

class PartTimeWorker
  def initialize(parts)
    @parts = parts
  end

  # 勤務予定を変更する処理
  def change_part(day, from_time, to_time)
    @parts = @parts.reject { |part| part.day == day }
    @parts << Part.new(day, from_time, to_time)
  end
end

class Part
  attr_accessor :day, :from_time, :to_time
  # ...
end

パートタイム従業員個人の処理はこれで問題はない。しかし、「常に2人の勤務でシフトを組まなければならない」という調整ができていない。

以下のように他の従業員を持つことで要求は満たせる。

class PartTimeWorker
  def initialize(parts, part_time_workers)
    @parts = parts
    @part_time_workers = part_time_workers
  end

  # 勤務予定を変更する処理
  def change_part(day, from_time, to_time)
    # パート全員の勤務予定を確認している
    worker_count = @part_time_workers.count do |worker|
      worker.work?(day, from_time, to_time)
    end
    raise 'シフトが埋まっている' if 2 <= worker_count

    @parts = @parts.reject { |part| part.day == day }
    @part << Part.new(day, from_time, to_time)
  end

  def work?(day, from_time, to_time)
    # 指定時間に働いているか?
  end
end

しかし、さらに「社員が2人以下しか勤務できない場合はパートは3人シフトになる」「繁忙期は社員3人、パート4人のシフトになる」といった追加要求が発生した場合に非常に面倒なことになる。

PartTimeWorkerクラスは社員(Employeeクラス)の勤務状況を知らなければならないし、繁忙期の情報を知らなければいけなくなる。Employeeクラスにおいても繁忙期の情報は知らなければいけない上、その処理はほとんどPartTimeWorkerクラスと同じはずである。

そこでMediatorパターンを使う。

class ScheduleMediator
  def initialize(part_time_workers, employees, busy_periods)
    @part_time_workers = part_time_workers
    @employees = employees
    @busy_periods = busy_periods
  end

  # パートのシフトが埋まっているか
  def part_time_workers_shift_full?(day, from_time, to_time)
    require_count = headcount_of_require_part_time_workers(day, from_time, to_time)
    worker_count = headcount_of_part_time_workers(day, from_time, to_time)
    require_count <= worker_count
  end

  # 社員のシフトが埋まっているか
  def employees_shift_full?(day, from_time, to_time)
    require_count = headcount_of_require_employees(day, from_time, to_time)
    worker_count = headcount_of_employees(day, from_time, to_time)
    require_count <= worker_count
  end

  # 繁忙期か?
  def on_busy_period?(day, from_time, to_time)
    # 繁忙期ならtrue
  end

  # 指定時間に勤務予定のパート人数
  def headcount_of_part_time_workers(day, from_time, to_time)
    @part_time_workers.count { |worker| worker.work?(day, from_time, to_time) }
  end

  # 指定時間に勤務予定の社員人数
  def headcount_of_employees(day, from_time, to_time)
    @employees.count { |worker| worker.work?(day, from_time, to_time) }
  end

  # 指定時間に必要なパート人数
  def headcount_of_require_part_time_workers(day, from_time, to_time)
    return 4 if on_busy_period?(day, from_time, to_time)
    return 3 if headcount_of_employees(day, from_time, to_time) <= 2

    2
  end

  # 指定時間に必要な社員人数
  def headcount_of_require_employees(day, from_time, to_time)
    return 3 if on_busy_period?(day, from_time, to_time)

    0
  end
end

class PartTimeWorker
  # mediatorはScheduleMediatorのインスタンス
  def initialize(parts, mediator)
    @parts = parts
    @mediator = mediator
  end

  # 勤務予定を変更する処理
  def change_part(day, from_time, to_time)
    if @mediator.part_time_workers_shift_full?(day, from_time, to_time)
      raise 'シフトが埋まっている'
    end

    @parts = @parts.reject { |part| part.day == day }
    @part << Part.new(day, from_time, to_time)
  end

  def work?(day, from_time, to_time)
    # 指定時間に働いているか?
  end
end

これによって、PartTimeWorkerクラスは社員や繁忙期などを気にする必要がなくなった。参照しなければならない情報が増えたり条件が変更されたとしても、それらの修正はScheduleMediatorクラスだけで済む。