Facadeパターン(GoF)
参考書籍一覧(Amazon アソシエイトリンク)
以下のような時に適用するパターン。
- 複雑なクラス関係の取り扱いや手続きを
- 意識せずに済むようにしたい場合
- 定型処理として隔離したい場合
- 主題の処理から切り離したい場合
人事評価システムを開発していて、評価結果を表示する処理を以下のように書いたところだとする。
class AssessmentsController < ApplicationController
# 評価確定前のプレビュー
# Employee, ManagerAssessmentはRDBへの問い合わせクラス(ActiveRecord)
# AssessmentLogic, ManagerAssessmentLogicは評価計算のクラス(Entity、ValueObject、DomainModel)
# ManagerAssessmentは上司の評価とする
def preview
employee = Employee.find(params[:employee_id])
assessment = AssessmentLogic.new(employee.id, employee.name, employee.rank)
manager_assessments = ManagerAssessment.where(employee_id: employee, year: params[:year])
manager_assessments.each do |manager_assessment|
manager_assessment_logic = ManagerAssessmentLogic.new(manager_assessment.xxx_score,
manager_assessment.yyy_score,
manager_assessment.zzz_score)
assessment.manager_assessments << manager_assessment_logic
end
@result_score = assessment.score
end
def conclusion
# 評価の確定。previewアクションとの違いは評価を保存するかどうかだけ
# 重複するので省略...
end
end
これはいわゆるFat Controllerというもので、Controllerの主な責務であるどのViewを表示するかやViewに値を渡すことの他に、具体的な評価処理がpreview
アクションのコードに含まれてしまっている。
また、preview
アクションとconclusion
アクションで同じ処理が繰り返されてしまっている。privateメソッドを作って、共通処理を押し込むことも可能だが、Controllerの責務外の処理であることは変わりがない。
これらの問題に対処するため、以下のようにする。
class AssessmentsController < ApplicationController
# 評価確定前のプレビュー
def preview
employee = Employee.find(params[:employee_id])
@result_score = AssessmentService.new(employee).score(params[:year])
end
def conclusion
employee = Employee.find(params[:employee_id])
result_score = AssessmentService.new(employee).score(params[:year])
# 評価を保存する処理が続く...
end
end
class AssessmentService
def initialize(employee)
@employee = employee
end
def score(year)
assessment = AssessmentLogic.new(@employee.id, @employee.name, @employee.rank)
manager_assessments = ManagerAssessment.where(employee_id: @employee, year: year)
manager_assessments.each do |manager_assessment|
manager_assessment_logic = ManagerAssessmentLogic.new(manager_assessment.xxx_score,
manager_assessment.yyy_score,
manager_assessment.zzz_score)
assessment.manager_assessments << manager_assessment_logic
end
assessment.score
end
end
これにより、Controller側にとって複雑な処理を意識せずに済むようになり、主要な責務でない処理を切り離すことができた。また、定型処理を隔離することができた。
conclusion
アクションの評価の保存処理が複雑なら、同じようにServiceクラスを作るか、AssessmentService
クラスにconclusion
メソッドを追加してしまえば良い。