見出し画像

Pythonで解説するソフトウェア開発のデザインパターン1_生成に関するパターン #447

これまで機会がなく体型的に調べていなかった「デザインパターン」について、きっかけがあったので一度まとめておこうと思います。

デザインパターンは必ずしも設計の最適解ではないそうですが、実際に一通り見てみて、エンジニアとして抑えておくべき知見に満ちているなと感じました。最近学んでいるデータ構造とアルゴリズムもそうですが、基礎ほどしっかりとやるべきですね。

しっかり理解しながら進めているとなかなか時間がかかってしまっており、今回は「生成に関するパターン」のみです。

それでは整理していきます。

デザインパターンとは

まず、そもそもデザインパターンとは何か、です。以下はWikipediaからの引用です。

ソフトウェア開発におけるデザインパターンまたは設計パターン(英: design pattern)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。

具体的には、大きく以下の分類があります。

  • 生成に関するパターン

  • 構造に関するパターン

  • 振る舞いに関するパターン

  • 並行処理に関するパターン

確かに実際にアプリケーション開発をしていると、引き出しというか、「こういう時にこういう方法がある」というアイデアを持ってるだけでも強いなと感じます。

デザインパターンとはその良質な引き出しになるためのものです。

生成に関するパターン

オブジェクト生成に関するデザインパターンは、コードの柔軟性と再利用性を高める仕組みを提供しています。

Factory Method

元となる親クラスでオブジェクトの型(インターフェース)を定義し、オブジェクト作成自体はサブクラスで行う、という流れを取ることで、オブジェクトのインスタンス化を抽象化するデザインパターンです

これにより、クライアントコードはオブジェクトに自身の特性を付与することに集中でき、様々なオブジェクトを柔軟に生成できます

例として、「椅子」を抽象クラスとして考えます。この抽象クラスは、「座る」という基本的な機能を定義します。具体的な「椅子」の種類、例えば「西洋風の椅子」や「和風の椅子」は、この抽象クラスを継承し、それぞれ独自の特徴を持つ「座る」機能を実装します。

Factory Methodパターンでは、椅子オブジェクトの生成を行うファクトリーメソッドを抽象クラス内に定義し、サブクラスでメソッドをオーバーライドして具体的なオブジェクトの生成を行います。

Pythonで例示します。

# 抽象クラス(ファクトリーメソッドを含む)
class Chair(ABC):
    # ファクトリーメソッド。具象化する際にサブクラスで定義が必要
    @abstractmethod
    def sitDown(self):
        pass  # 抽象メソッド

    # その他のメソッド。デフォルト実装または抽象メソッド
    def setChair(self):
        pass
    def moveChair(self):
        pass


# プロダクトインターフェース。具象プロダクトが必ず実装しなければならない操作
class SittingBehavior:
    def rest(self):
        pass
    def focus(self):
        pass


# 具象プロダクト
class WesternSitDown(SittingBehavior):
    def rest(self):
        pass            # 実装する
    def focus(self):
        pass            # 実装する


# 具象プロダクト
class JapaneseSitDown(SittingBehavior):
    def rest(self):
        pass            # 実装する
    def focus(self):
        pass            # 実装する


# サブクラス(具象クリエイター)は抽象クラスを上書きして具象プロダクトを作成!!!!!!
class WesternChair(Chair):
    def sitDown(self):
        return WesternSitDown()


class JapaneseChair(Chair):
    def sitDown(self):
        return JapaneseSitDown()

このアプローチにより、新しい種類の「椅子」をシステムに追加する際にも、既存のコードを変更せずに拡張が可能となり、オブジェクト生成のプロセスをより柔軟に管理できるようになります。


Abstract Factory

関連するオブジェクト群を一纏めに生成できることを意図したインターフェースを提供するデザインパターンです。

例えば「椅子」「机」「食器」という3つの関連オブジェクトがあるとします。これを「ダイニング」という抽象ファクトリー(Abstract Factory)で一纏めにしておくことで、3つのオブジェクトを漏れなく生成できます。

これにより、例えば「西洋風ダイニング」や「和風ダイニング」として具象化したい際、「西洋風の椅子」と「和風の机」が混在して生成されることを防げます。

Pythonで例示します。

from abc import ABC, abstractmethod

# ダイニングファクトリーの抽象クラス(Abstract Factory)
class DiningFactory(ABC):
    @abstractmethod
    def createChair(self):        # これはファクトリーメソッド。次のサブクラスで具象化される
        pass

    @abstractmethod
    def createTable(self):        # これはファクトリーメソッド。次のサブクラスで具象化される
        pass

    @abstractmethod
    def createTableware(self):    # これはファクトリーメソッド。次のサブクラスで具象化される
        pass


# 西洋風ダイニングファクトリー
class WesternDiningFactory(DiningFactory):
    def createChair(self):
        return WesternChair()
    def createTable(self):
        return WesternTable()
    def createTableware(self):
        return WesternTableware()

# 和風ダイニングファクトリー
class JapaneseDiningFactory(DiningFactory):
    def createChair(self):
        return JapaneseChair()
    def createTable(self):
        return JapaneseTable()
    def createTableware(self):
        return JapaneseTableware()


# 各製品の抽象基底クラス
class Chair(ABC):
    pass
class Table(ABC):
    pass
class Tableware(ABC):
    pass

# 具体的な製品クラス
class WesternChair(Chair):
    pass
class WesternTable(Table):
    pass
class WesternTableware(Tableware):
    pass

# 具体的な製品クラス
class JapaneseChair(Chair):
    pass
class JapaneseTable(Table):
    pass
class JapaneseTableware(Tableware):
    pass

Abstract Factoryパターンは、しばしばFactory Methodを内部的に使用します。ただしAbstract Factoryは複数のFactory Methodの集合体というわけではなく、関連オブジェクト群を生成するための統一されたインターフェースを提供する点で独自の役割を持っています。


Builder

オブジェクトの構築プロセスを分離するデザインパターンです。オブジェクトを構成するコンポーネントが多く、構築の順序が複雑な場合に役立ちます。

以下の要素で構成されます。

  • Builder: オブジェクトの各パーツを構築するためのインターフェース

  • Concrete Builder: Builderインターフェースの実装で最終的なオブジェクトを提供

  • Director: Concrete Builderが提供するインターフェースを利用してオブジェクトの構築を指示

  • Product: 構築される最終的なオブジェクト

Pythonで例示します。

from abc import ABC, abstractmethod

# Builder
class VehicleBuilder(ABC):
    @abstractmethod
    def set_wheels(self):
        pass
    @abstractmethod
    def set_seats(self):
        pass
    @abstractmethod
    def set_structure(self):
        pass

    def get_result(self):
        pass


# Concrete Builder
class CarBuilder(VehicleBuilder):
    def __init__(self):
        self.vehicle = Vehicle("Car")
    def set_wheels(self):
        self.vehicle.wheels = 4
    def set_seats(self):
        self.vehicle.seats = 5
    def set_structure(self):
        self.vehicle.structure = "Car Structure"
    def get_result(self):
        return self.vehicle


# Concrete Builder
class BikeBuilder(VehicleBuilder):
    def __init__(self):
        self.vehicle = Vehicle("Bike")
    def set_wheels(self):
        self.vehicle.wheels = 2
    def set_seats(self):
        self.vehicle.seats = 2
    def set_structure(self):
        self.vehicle.structure = "Bike Structure"
    def get_result(self):
        return self.vehicle


# Product
class Vehicle:
    def __init__(self, vehicle_type):
        self.vehicle_type = vehicle_type
        self.wheels = None
        self.seats = None
        self.structure = None

    def __str__(self):
        return f"{self.vehicle_type} with {self.wheels} wheels, {self.seats} seats, and {self.structure}"


# Director
class VehicleDirector:
    def __init__(self, builder):
        self._builder = builder
    def construct(self):
        self._builder.set_wheels()
        self._builder.set_seats()
        self._builder.set_structure()
    def get_vehicle(self):
        return self._builder.get_result()


# 使用例
car_builder = CarBuilder()
director = VehicleDirector(car_builder)
director.construct()
car = director.get_vehicle()
print(car)  # "Car with 4 wheels, 5 seats, and Car Structure"

bike_builder = BikeBuilder()
director = VehicleDirector(bike_builder)
director.construct()
bike = director.get_vehicle()
print(bike)  # "Bike with 2 wheels, 2 seats, and Bike Structure"


Prototype

既存のオブジェクトをコピーして新しいオブジェクトを生成することを目的としたデザインパターンです。他のデザインパターンではオブジェクト生成の過程を共通化するなどしていますが、Prototypeでは既存のオブジェクトをテンプレートにして複製します。

これは、オブジェクトの初期化にコストがかかる際に有効です。

Pythonで例示します。

import copy

# Prototype
class Furniture:
    def __init__(self, name, material):
        self.name = name
        self.material = material

    def clone(self):
        # 自身の深いコピーを作成して返す
        return copy.deepcopy(self)

    def __str__(self):
        return f"{self.name} made of {self.material}"

# Concrete Prototypes
class Chair(Furniture):
    def __init__(self, material):
        super().__init__("Chair", material)

class Table(Furniture):
    def __init__(self, material):
        super().__init__("Table", material)

# Clientの使用例
original_chair = Chair("Wood")
print(original_chair)  # Chair made of Wood


# オブジェクトのコピーを作成し、クローンの材質を変更
cloned_chair = original_chair.clone()
cloned_chair.material = "Plastic"
print(cloned_chair)  # Chair made of Plastic

# 別のオブジェクトで試す
original_table = Table("Glass")
print(original_table)  # Table made of Glass

# オブジェクトのコピーを作成し、クローンの材質を変更
cloned_table = original_table.clone()
cloned_table.material = "Metal"
print(cloned_table)  # Table made of Metal


Singleton (シングルトン)

クラスのインスタンスが1つだけ生成され、そのインスタンスへのグローバルなアクセスポイントを提供することを目的としたデザインパターンです。このパターンは、システム全体で共有されるリソースや設定など、一元的に管理する必要があるオブジェクトに対して特に有用です。

以下の特徴を備えています。

  • コンストラクタがプライベートで、外部から直接インスタンス化するのを防ぐ

  • クラス内に静的なメソッドを用意し、そのメソッドを通じて唯一のインスタンスにアクセスする

  • クラス内に唯一のインスタンスを保持するための静的な変数を持つ

Pythonで例示します。

class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls, *args, **kwargs)
        return cls._instance

    # その他のメソッドや属性をここに定義できます

# Singletonクラスのインスタンス化
singleton1 = Singleton()
singleton2 = Singleton()

# 両方のインスタンスが同一であることを確認
assert singleton1 is singleton2
print("singleton1とsingleton2は同一のインスタンスです。")

Pythonでは、Singletonパターンをいくつかの方法で実装することができますが、一般的なアプローチの一つは__new__メソッドをオーバーライドすることです。

__new__メソッドはクラスのインスタンスを生成する最初のステップであるため、ここでクラスのインスタンス化プロセスを細かく制御できます。

ここでは__new__メソッド内でクラスレベルの変数(_instance)を使用して唯一のインスタンスを保持し、そのクラスのインスタンスが一つしか存在しないことを保証します。これにより、クラスが複数回インスタンス化されても常に同じインスタンスが返され、新しいインスタンスの作成が防がれます。


生成に関するパターンは以上です。
ここまでお読みいただきありがとうございました!

参考


この記事が気に入ったらサポートをしてみませんか?