factory_botと学ぶデザインパターン【Strategyパターン】
最近factory_botのコードを読み始めました。
factory_botにはいくつかのデザインパターンが実装されていて、create
やbuild
メソッドの定義には、Strategyパターンが使われています。
factory_botのコードを追いながら、Strategyパターンについて学ぶ記事です。
Strategyパターンの紹介
ポリモーフィズムを使ってアルゴリズムの切り替えを可能にするデザインパターンです。簡略化したコードでは以下のようなイメージです。呼び出し側は、状況に応じてどのアルゴリズムを使うかを選択してアルゴリズムの切り替えができます。
class AlgorithmA
def print_name
p 'AlgorithmA'
end
end
class AlgorithmB
def print_name
p 'AlgorithmB'
end
end
class Main
attr_reader :algorithm
def initialize(algorithm)
@algorithm = algorithm
end
def execute
algorithm.print_name
end
end
# Client
Main.new(AlgorithmA.new).execute
Main.new(AlgorithmB.new).execute
もしStrategyパターンを使わないとすると条件分岐がMain#execute
の中に入ってきます。
class Main
def execute
if some_condition
p 'AlgorithmA'
else
p 'AlgorithmB'
end
end
end
こうなるとアルゴリズムのパターンが増えたときには条件分岐が増え、可読性・保守性が下がります。なので、execute
の中の条件分岐を外(AlgorithmA
, AlgorithmB
)に追いやり、責務を移譲するのがStrategyパターンです。
結城浩さんのデザインパターンの本では、じゃんけんの戦略を呼び出し側で切り替えする実装例が紹介されています。
factory_botのcreate
メソッド
factory_botのcreate
やbuild
などのメソッドの箇所でStrategyパターンが使われています。実装の詳細を確認する前に、factrory_botの使い方についておさらいします。
factrory_botを使うと以下のように書いて、インスタンスの生成ができます。
let(:user) { create(:user) }
create
メソッドは、factory_botがFactoryBot::Syntax::Methods
モジュールに定義されているメソッドです。include することによって使えるようになります。
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
Strategyパターンを使ったcreate
メソッドの定義
FactoryBot::Syntax::Methods
にcreate
メソッドが直接書かれている訳ではなく、実装は外からdefine_method
によって定義されています。Strategyパターンを使って実装されています。
FactoryBot::Internal.register_default_strategies
で、メソッド名に対応するクラスを指定しています。
def register_default_strategies
register_strategy(:build, FactoryBot::Strategy::Build)
register_strategy(:create, FactoryBot::Strategy::Create)
register_strategy(:attributes_for, FactoryBot::Strategy::AttributesFor)
register_strategy(:build_stubbed, FactoryBot::Strategy::Stub)
register_strategy(:null, FactoryBot::Strategy::Null)
end
各メソッドをFactoryBot::Syntax::Methods
に定義するのは、lib/factory_bot/strategy_syntax_method_registrar.rb
で行われます。
def define_singular_strategy_method
strategy_name = @strategy_name
define_syntax_method(strategy_name) do |name, *traits_and_overrides, &block|
FactoryRunner.new(name, strategy_name, traits_and_overrides).run(&block)
end
end
def define_syntax_method(name, &block)
FactoryBot::Syntax::Methods.module_exec do
if method_defined?(name) || private_method_defined?(name)
undef_method(name)
end
define_method(name, &block)
end
end
簡単に処理の流れを図示したものが以下になります。
なぜStrategyパターンが使われているのかを自分なりに考えてみると、以下の理由かなと思います。
trait
を使う、_list
のメソッドも合わせて定義するなど、基本的にやりたいことは同じ- ただ処理の中身は各メソッドで差異があるので、アルゴリズムを切り替えたい
最後に
factory_botで実装されているStrategyパターンを見てみました。
factory_botでは他にもObserverパターンなども入っているので、こちらも続編記事などで紹介したいと思います。