このセクションでは、デザインパターンの中でも「オブジェクトの作り方」にフォーカスしたファクトリメソッドパターンを学びます。
クラス名を直接書いてインスタンス化していると、新しい種類を追加するたびにコードを直す羽目になっていませんか?
そんな小さな不便が積み重なると、保守がつらくなります。 そこで役に立つのが、オブジェクト生成のやり方を整理するファクトリメソッドパターンです。
ここでは、あえて“よくある書き方”からスタートして課題を体感してみます。 次のように、必要な場所で直接クラスを呼び出すやり方は、最初はとてもシンプルです。
class Sedan:
def drive(self):
return "Driving a sedan!"
class SUV:
def drive(self):
return "Driving an SUV!"
car = Sedan() # ここで具体クラスを直接指定
print(car.drive())
一見簡単ですが、もし「トラック」を追加したくなったらどうでしょう?クライアント側のあちこちに書かれた Sedan()
や SUV()
を探して置き換える必要が出てきます。
これが数箇所ならまだしも、規模が大きくなるほど手戻りが増え、テストのコストも上がってしまいます。
ここからは、本題の目的を初心者にもわかりやすい言葉で整理します。 抽象的な言い回しより「何ができるか」に目を向けると理解しやすくなります。
具体的な実践例で見ていきましょう。
いきなり“本来の”ファクトリメソッドパターンに入るより、初心者の方は「1つのファクトリクラスが条件によって適切なクラスを返す」形(いわゆるシンプルファクトリ)から掴むと理解がスムーズです。
ここでは車を例にします。
from abc import ABC, abstractmethod
from typing import Type, Dict
# 1) 共通インターフェース
class Car(ABC):
@abstractmethod
def drive(self) -> str:
...
# 2) 具体クラス
class Sedan(Car):
def drive(self) -> str:
return "Driving a sedan!"
class SUV(Car):
def drive(self) -> str:
return "Driving an SUV!"
# 3) ファクトリ(シンプルな実装)
class CarFactory:
def __init__(self) -> None:
# 車種名とクラスの対応表
self._registry: Dict[str, Type[Car]] = {
"sedan": Sedan,
"suv": SUV,
}
def create_car(self, car_type: str) -> Car:
try:
cls = self._registry[car_type.lower()]
except KeyError:
raise ValueError(f"Unknown car type: {car_type}")
return cls()
# 4) 使う側(クライアント)
factory = CarFactory()
car1 = factory.create_car("sedan")
print(car1.drive()) # -> Driving a sedan!
car2 = factory.create_car("suv")
print(car2.drive()) # -> Driving an SUV!
ここで大事なのは「使う側は具体的なクラス名(SedanやSUV)を知らなくてもよい」という点です。CarFactoryに「sedan」や「suv」といったラベルを渡すだけで、適切なインスタンスが返ってきます。新しい種類を追加したいときは、クラスを1つ作ってレジストリ(対応表)に1行足せばOKです。if文がズラリと並ぶコードより、追加・変更が分かりやすくなります。
このコードでは、Carという抽象クラスが「車ならdriveを持っているべき」という約束を表しています。SedanやSUVはその約束を守って具体的な動きを提供します。ファクトリは「どのクラスを使うか」という悩みを引き受け、クライアントは「何が欲しいか」だけを伝えます。これにより、クライアントのコードから具体クラス名が消え、読みやすさとテストのしやすさが上がります。
次は、GoF(Gang of Four)で定義される「ファクトリメソッドパターン」に寄せた形を見てみましょう。ポイントは「生成のためのメソッドを持つ基底クラスがあり、そのメソッドをサブクラスがオーバーライドして、どの具体クラスを作るかを決める」ことです。
from abc import ABC, abstractmethod
# Product
class Car(ABC):
@abstractmethod
def drive(self) -> str:
...
class Sedan(Car):
def drive(self) -> str:
return "Driving a sedan!"
class SUV(Car):
def drive(self) -> str:
return "Driving an SUV!"
# Creator(工場のひな型)
class CarStore(ABC):
# ファクトリメソッド:サブクラスが「何を作るか」を決める
@abstractmethod
def create_car(self) -> Car:
...
# 共通の手順を用意して、生成だけサブクラスに任せる
def order_car(self) -> str:
car = self.create_car()
return car.drive()
# ConcreteCreator(工場の具体実装)
class SedanStore(CarStore):
def create_car(self) -> Car:
return Sedan()
class SUVStore(CarStore):
def create_car(self) -> Car:
return SUV()
sedan_store = SedanStore()
print(sedan_store.order_car()) # -> Driving a sedan!
suv_store = SUVStore()
print(suv_store.order_car()) # -> Driving an SUV!
ここでは、CarStoreに「車を注文する」という共通の流れをまとめ、実際にどの車を作るか(create_car)はサブクラス(SedanStoreやSUVStore)に委ねています。クライアントは「どのストア(Creator)に注文するか」を選ぶだけで、具体クラスの生成はストア側に隠されます。これが「生成の委譲」による柔軟性です。
💡 豆知識:GoFとは? GoF(Gang of Four)とは、ソフトウェア設計の世界的な名著 『Design Patterns: Elements of Reusable Object-Oriented Software(デザインパターン ― 再利用のためのオブジェクト指向設計)』 の著者4人を指します。この4人の名前の頭文字をとって「Gang of Four(四人組)」=GoFと呼ばれています。
ファクトリメソッドパターンにおけるCreator(クリエイター)とは、オブジェクト生成の流れを定義する抽象クラスのことです。
実際に「どのクラスを作るか」は知らず、生成の手順だけを決めます。たとえば CarStore
は、order_car()
で「車を注文して返す」という共通の流れを持ちながら、実際に作る車(Sedan
や SUV
)はサブクラスに任せています。
このように、Creatorは「生成の仕組み」を定義し、サブクラス(ConcreteCreator
)が「何を作るか」を実装します。
「Store(ストア)」とは、Creatorを“お店”のように見立てたものです。クライアントは「どんな車が欲しいか」を注文するだけで、具体的なクラスを意識せずに使えます。 この設計により、生成手順の共通化と拡張性を両立し、新しい種類を追加しても既存コードをほとんど修正せずに済む柔軟な構造が実現できます。
ここまで読んで「どっちを使えばいいの?」と感じた方も多いはず。使い分けの目線を簡潔にまとめます。
どちらも「クライアントから具体クラス名を隠す」というゴールは同じです。プロジェクトの規模や変更の頻度に応じて選びましょう。
ここでは、先ほどのシンプルファクトリ版に「トラック」を足すケースを想像してみます。変更は驚くほど小さくできます。
これだけで、クライアント側は "truck"
を指定するだけで新しい車を使えるようになります。もしあなたが「追加するたびに既存コードを触るのが怖い」と感じているなら、ファクトリを導入する価値は大きいはずです。
ファクトリメソッドパターンは、「生成を任せる」というたった1つの工夫でコードの保守性と拡張性を大きく高めます。最初はシンプルファクトリから始めてもOKです。必要に応じてCreator/ConcreteCreatorの構成に発展させれば、共通手順と生成の責務分離がさらに進みます。
「新しい機能を足すたびに怖い」と感じるコードが、ほんの少しの設計工夫で落ち着きを取り戻します。まずは小さな場面で試して、理解を深めていきましょう。