Menu
Coddy logo textTech

Factory パターン

CoddyのPythonジャーニー「Object Oriented Programming」セクションの一部 — レッスン 46/64。

ファクトリパターンは、具体的なクラスを指定せずにオブジェクトを生成します。コンストラクタを直接呼び出す代わりに、どのクラスをインスタンス化するかを決定するファクトリメソッドを使用します。

以下はシンプルな製品クラスです:

class Car:
    def __init__(self, brand):
        self.brand = brand
        self.type = "Car"
    
    def info(self):
        return f"{self.type}: {self.brand}"

class Bike:
    def __init__(self, brand):
        self.brand = brand
        self.type = "Bike"
    
    def info(self):
        return f"{self.type}: {self.brand}"

これらのオブジェクトを生成するためのファクトリクラスを作成します:

class VehicleFactory:
    def create_vehicle(self, vehicle_type, brand):
        if vehicle_type == "car":
            return Car(brand)
        elif vehicle_type == "bike":
            return Bike(brand)
        else:
            raise ValueError(f"Unknown type: {vehicle_type}")

コンストラクタを直接呼び出す代わりに、ファクトリを使用してください:

factory = VehicleFactory()
my_car = factory.create_vehicle("car", "Toyota")
my_bike = factory.create_vehicle("bike", "Honda")

print(my_car.info())   # Car: Toyota
print(my_bike.info())  # Bike: Honda

*args を使用して、ファクトリをより柔軟にします:

class FlexibleFactory:
    def create_vehicle(self, vehicle_type, *args):
        if vehicle_type == "car":
            return Car(args[0])  # ブランドのみ
        elif vehicle_type == "truck":
            return Truck(args[0], args[1])  # ブランドと積載量
        else:
            raise ValueError(f"Unknown type: {vehicle_type}")

class Truck:
    def __init__(self, brand, capacity):
        self.brand = brand
        self.capacity = capacity
        self.type = "Truck"
    
    def info(self):
        return f"{self.type}: {self.brand} ({self.capacity}t)"

フレキシブルなファクトリを使用します:

flexible = FlexibleFactory()
car = flexible.create_vehicle("car", "Ford")
truck = flexible.create_vehicle("truck", "Volvo", "20")

print(car.info())    # 車: Ford
print(truck.info())  # トラック: Volvo (20t)

出力:

Car: Toyota
Bike: Honda
Car: Ford
Truck: Volvo (20t)

重要なポイント: Factoryパターンを使用すると、正確なクラスを知らなくてもオブジェクトを作成できます。ファクトリメソッドは、パラメータに基づいてインスタンス化するクラスを決定します。異なるコンストラクタパラメータを持つプロダクトを処理するには、*argsを使用します。これにより、コードの柔軟性が高まり、新しいプロダクトタイプでの拡張が容易になります。

challenge icon

チャレンジ

中級

このチャレンジでは、継承とポリモーフィズムを用いた適切なオブジェクト指向設計を使用して、図形ファクトリシステムを実装します。

以下のファイルの実装を完成させてください:

  • shape.py - 基本の Shape クラス
  • circle.py - Circle の実装
  • rectangle.py - Rectangle の実装
  • triangle.py - Triangle の実装
  • shapefactory.py - 図形を作成するためのファクトリクラス

各ファイルには、実装のガイドとなる詳細な TODO コメントが含まれています。コードがすべての要件を満たすように、これらのコメントに注意深く従ってください。

チートシート

Factoryパターンは、具体的なクラスを指定せずにオブジェクトを作成します。コンストラクタを直接呼び出す代わりに、どのクラスをインスタンス化するかを決定するFactoryメソッドを使用します。

基本的なFactoryの実装:

class VehicleFactory:
    def create_vehicle(self, vehicle_type, brand):
        if vehicle_type == "car":
            return Car(brand)
        elif vehicle_type == "bike":
            return Bike(brand)
        else:
            raise ValueError(f"Unknown type: {vehicle_type}")

# 使用例
factory = VehicleFactory()
my_car = factory.create_vehicle("car", "Toyota")
my_bike = factory.create_vehicle("bike", "Honda")

異なるコンストラクタ引数に対応するために *args を使用した柔軟なFactory:

class FlexibleFactory:
    def create_vehicle(self, vehicle_type, *args):
        if vehicle_type == "car":
            return Car(args[0])  # ブランドのみ
        elif vehicle_type == "truck":
            return Truck(args[0], args[1])  # ブランドと積載量
        else:
            raise ValueError(f"Unknown type: {vehicle_type}")

# 使用例
flexible = FlexibleFactory()
car = flexible.create_vehicle("car", "Ford")
truck = flexible.create_vehicle("truck", "Volvo", "20")

主な利点:Factoryパターンは、既存のクライアントコードを変更することなく、新しい製品タイプでコードをより柔軟に拡張しやすくします。

自分で試してみよう

from shapefactory import ShapeFactory
from shape import Shape
from circle import Circle
from rectangle import Rectangle
from triangle import Triangle
import sys

# テストケース実行プログラム
test_case = input()

factory = ShapeFactory()

if test_case == "circle_area":
    circle = factory.create_shape("circle", 5)
    print(f"{circle.area():.2f}")

elif test_case == "rectangle_perimeter":
    rectangle = factory.create_shape("rectangle", 4, 6)
    print(f"{rectangle.perimeter()}")

elif test_case == "triangle_perimeter":
    triangle = factory.create_shape("triangle", 3, 4, 5)
    print(f"{triangle.perimeter()}")

elif test_case == "invalid_shape":
    try:
        factory.create_shape("hexagon", 6)
        print("No exception raised")
    except ValueError as e:
        print(str(e))

elif test_case == "case_insensitive":
    circle = factory.create_shape("CiRcLe", 3)
    print(f"{circle.area():.2f}")

elif test_case == "shape_inheritance":
    shapes = [
        factory.create_shape("circle", 2),
        factory.create_shape("rectangle", 2, 3),
        factory.create_shape("triangle", 3, 4, 5)
    ]
    all_shapes = all(isinstance(shape, Shape) for shape in shapes)
    print(all_shapes)

elif test_case == "zero_radius_circle":
    circle = factory.create_shape("circle", 0)
    print(f"{circle.area():.2f} {circle.perimeter():.2f}")

elif test_case == "negative_dimensions":
    rectangle = factory.create_shape("rectangle", -2, -3)
    print(f"{rectangle.area()}")

elif test_case == "large_values":
    circle = factory.create_shape("circle", 1000000)
    print(f"{circle.area():.2e}")

elif test_case == "polymorphism_test":
    shapes = [
        factory.create_shape("circle", 2),
        factory.create_shape("rectangle", 3, 4),
        factory.create_shape("triangle", 3, 4, 5)
    ]
    area_sum = sum(shape.area() for shape in shapes)
    perimeter_sum = sum(shape.perimeter() for shape in shapes)
    print(f"Area sum: {area_sum:.2f}, Perimeter sum: {perimeter_sum:.2f}")

elif test_case == "triangle_area":
    triangle = factory.create_shape("triangle", 3, 4, 5)
    print(f"{triangle.area():.2f}")

elif test_case == "method_override":
    circle = factory.create_shape("circle", 2)
    rectangle = factory.create_shape("rectangle", 3, 4)
    triangle = factory.create_shape("triangle", 3, 4, 5)
    
    # 実装を比較するためにメソッドオブジェクトを取得する
    circle_area = Circle.area
    rectangle_area = Rectangle.area
    triangle_area = Triangle.area
    
    circle_perimeter = Circle.perimeter
    rectangle_perimeter = Rectangle.perimeter
    triangle_perimeter = Triangle.perimeter
    
    # すべての実装がユニークであるか確認する
    unique_areas = len({circle_area, rectangle_area, triangle_area}) == 3
    unique_perimeters = len({circle_perimeter, rectangle_perimeter, triangle_perimeter}) == 3
    
    if unique_areas and unique_perimeters:
        print("All shapes correctly override methods")
    else:
        print("Some shapes share method implementations")
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン