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
endStrategyパターンを使った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パターンなども入っているので、こちらも続編記事などで紹介したいと思います。