『オブジェクト指向実践ガイド』を読み、Strategyパターン・Factoryパターンを利用することで、拡張性・保守性が向上することを学んだ。
既存システムでStrategyパターンを利用している箇所があったが、本書で書かれていたものと比較するともう少し改善できそうな点があった。
どのように改善できそうか試してみる。
既存コード例(before)
現状だと以下のような問題を抱えています。
- 新しい仕様が追加されるたびにstrategyメソッドを修正する必要がある(Open/Closed原則違反)
- Strategizableモジュールが複数の仕様の知識を持っている(単一責任原則違反)
- 各仕様に関連するクラス群への直接的な依存(依存関係の問題)
- 全ての分岐をテストする必要がある(
テストの複雑さ)
↓のような感じのmoduleが定義されており、Hogeモデルにincludeして、hoge.strategy
のように呼び出しています。
module Hoge::Strategizable
extend ActiveSupport::Concern
Strategy = Data.define(
:updater,
:error_checker,
:exporter
)
def strategy
case hoge_hoge_id
when 1
Strategy.new(
updater: Hoge::Updater::Fuga1,
error_checker: Hoge::ErrorChecker::Fuga1,
exporter: Hoge::Exporter::Fuga1,
)
when 2
# ... 略
when 3
# ... 略
end
end
end
Strategyパターン/ Factoryパターン(after)
StrategyパターンとFactoryパターンを利用しても呼び出しはhoge.strategy
のままでよく、以下のメリットを得られるように変更することができました。
- 各Strategyクラスを独立してテストできる(
テストしやすい) - 新しい仕様の追加が容易(拡張性)
- 各仕様の変更が他に影響しない(保守性)
module Hoge::Strategizable
extend ActiveSupport::Concern
Strategy = Data.define(
:updater,
:error_checker,
:exporter
)
def strategy
Hoge::Strategy::Base.for(hoge_hoge_id)
end
end
# app/models/hoge/strategy/base.rb
module Hoge::Strategy
class Base
def self.for(hoge_hoge_id)
case hoge_hoge_id
when 1 then Fuga1.new
when 2 then Fuga2.new
when 3 then Fuga3.new
else raise "Unknown specification: #{hoge_hoge_id}"
end
end
def updater; raise NotImplementedError; end
def error_checker; raise NotImplementedError; end
def exporter; raise NotImplementedError; end
end
end
# app/models/hoge/strategy/fuga1.rb
class Hoge::Strategy::Fuga1 < Hoge::Strategy::Base
def updater
Hoge::Updater::Fuga1
end
def error_checker
Hoge::ErrorChecker::Fuga1
end
def exporter
Hoge::Exporter::Fuga1
end
end