5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GoFデザインパターン(覚書)

Last updated at Posted at 2023-11-02

1. はじめに

最近、改めてGoFデザインパターンの事を調べる機会があったので、Chat-GPTに助けてもらいながらPythonコードも含む資料を作成したので、自分もいつでも見れるようにQiitaに置いておきます。

1.1. GoFのデザインパターン

「GoF」は「Gang of Four」の略で、4人の著者(Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides)によって書かれた「Design Patterns: Elements of Reusable Object-Oriented Software」という本のことを指します。この本では、オブジェクト指向設計において再利用可能な設計の模範となる23のデザインパターンが紹介されています。

デザインパターンは、一般的な設計問題とそれに対する解決策を提供するもので、設計の品質を向上させ、コードの再利用性やメンテナンス性を高めることができます。

これらのパターンは、以下の3つのカテゴリに分類されています:

生成に関するパターン (Creational Patterns)
- Abstract Factory
- Builder
- Factory Method
- Prototype
- Singleton

構造に関するパターン (Structural Patterns)
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy

振る舞いに関するパターン (Behavioral Patterns)
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor

それぞれのパターンには、特定の目的や利用シーンがあり、適切に選択して利用することで効率的なシステム設計が可能となります。

2. 生成に関するパターン

生成に関するパターン(Creational Patterns)は、オブジェクトの生成に関する設計の問題を解決するためのものです。このカテゴリには以下の5つのデザインパターンが含まれます:

Abstract Factory (抽象工場)
- 目的:関連するオブジェクトの一連のファミリーを生成するインタフェースを提供する。
- 例:異なるOSごとのUI要素(ボタン、メニューなど)を生成する抽象工場。

Builder (ビルダー)
- 目的:オブジェクトの作成プロセスをカプセル化し、同じ作成プロセスで異なる表現を生成する。
- 例:テキストエディタがリッチテキストやプレーンテキストといった異なる形式のドキュメントを生成する場合。

Factory Method (ファクトリメソッド)
- 目的:インスタンスの生成をサブクラスに委譲する。
- 例:異なる種類のログのオブジェクトを生成するためのログファクトリ。

Prototype (プロトタイプ)
- 目的:既存のオブジェクトをコピーして新しいオブジェクトを生成する。
- 例:グラフィックエディタで図形をコピー&ペーストする場合。

Singleton (シングルトン)
- 目的:クラスに一つだけインスタンスを持ち、そのインスタンスへのグローバルなアクセスポイントを提供する。
- 例:システムの設定やログの管理など、一箇所からのアクセスが必要なリソース。

これらの生成パターンは、システムの構造や独立性を維持しながら、オブジェクトの生成方法を柔軟に変更したり、新しいタイプのオブジェクトを簡単に追加したりすることができます。

2.1.Abstract Factory パターン

Abstract Factory パターンは、関連する一連のオブジェクトファミリーを作成するためのインターフェースを提供するデザインパターンです。具体的なクラスを指定せずに、そのファミリーのオブジェクトを生成できるようにすることが目的です。

例: UIコンポーネントの生成

2つの異なるUIスタイル(LightThemeDarkTheme)のボタンとメニューを考えます。

  1. ButtonMenu というインターフェースを持つクラスを定義します。
  2. これらのインターフェースに基づいて、LightThemeButton, DarkThemeButton, LightThemeMenu, DarkThemeMenu という具体的なクラスを定義します。
  3. これらのオブジェクトを生成するための抽象工場(AbstractUIFactory)を定義します。
  4. LightThemeUIFactoryDarkThemeUIFactory という2つの具体的なファクトリを定義します。

Pythonでの実装

from abc import ABC, abstractmethod

# Step 1: UIコンポーネントのインターフェースを定義
class Button(ABC):
    @abstractmethod
    def render(self):
        pass

class Menu(ABC):
    @abstractmethod
    def render(self):
        pass

# Step 2: 具体的なUIコンポーネントを定義
class LightThemeButton(Button):
    def render(self):
        return "Light Theme Button"

class DarkThemeButton(Button):
    def render(self):
        return "Dark Theme Button"

class LightThemeMenu(Menu):
    def render(self):
        return "Light Theme Menu"

class DarkThemeMenu(Menu):
    def render(self):
        return "Dark Theme Menu"

# Step 3: 抽象工場の定義
class AbstractUIFactory(ABC):
    @abstractmethod
    def create_button(self) -> Button:
        pass
    
    @abstractmethod
    def create_menu(self) -> Menu:
        pass

# Step 4: 具体的なファクトリを定義
class LightThemeUIFactory(AbstractUIFactory):
    def create_button(self) -> Button:
        return LightThemeButton()
    
    def create_menu(self) -> Menu:
        return LightThemeMenu()

class DarkThemeUIFactory(AbstractUIFactory):
    def create_button(self) -> Button:
        return DarkThemeButton()
    
    def create_menu(self) -> Menu:
        return DarkThemeMenu()

# 使用例
factory = DarkThemeUIFactory()
button = factory.create_button()
menu = factory.create_menu()
print(button.render())  # 出力: Dark Theme Button
print(menu.render())    # 出力: Dark Theme Menu

この例では、異なるテーマのUIコンポーネントを一貫して生成する方法を示しています。Abstract Factory パターンを使用することで、新しいテーマやコンポーネントを追加する際の変更や拡張が容易になります。

2.2.Builder パターン

Builder パターンは、複雑なオブジェクトの作成プロセスをカプセル化するデザインパターンです。オブジェクトの内部表現と作成プロセスを分離することで、同じ作成プロセスで異なる表現を持つオブジェクトを生成できるようにします。

例: ドキュメントの生成

以下は、HTMLドキュメントとMarkdownドキュメントを生成するビルダーの例です。

  1. DocumentBuilder という抽象ビルダークラスを定義します。
  2. この抽象ビルダーに基づいて、HTMLDocumentBuilderMarkdownDocumentBuilder という具体的なビルダークラスを定義します。
  3. Director クラスを定義して、ビルダーを使ってドキュメントを構築します。

Pythonでの実装

from abc import ABC, abstractmethod

# Step 1: ドキュメントビルダーのインターフェースを定義
class DocumentBuilder(ABC):
    @abstractmethod
    def add_title(self, title: str):
        pass

    @abstractmethod
    def add_paragraph(self, paragraph: str):
        pass

    @abstractmethod
    def get_result(self) -> str:
        pass

# Step 2: 具体的なビルダーを定義
class HTMLDocumentBuilder(DocumentBuilder):
    def __init__(self):
        self.parts = []

    def add_title(self, title: str):
        self.parts.append(f"<h1>{title}</h1>")

    def add_paragraph(self, paragraph: str):
        self.parts.append(f"<p>{paragraph}</p>")

    def get_result(self) -> str:
        return "\n".join(self.parts)

class MarkdownDocumentBuilder(DocumentBuilder):
    def __init__(self):
        self.parts = []

    def add_title(self, title: str):
        self.parts.append(f"# {title}")

    def add_paragraph(self, paragraph: str):
        self.parts.append(paragraph)

    def get_result(self) -> str:
        return "\n\n".join(self.parts)

# Step 3: Director クラスを定義
class Director:
    def __init__(self, builder: DocumentBuilder):
        self.builder = builder

    def construct(self, title: str, paragraph: str):
        self.builder.add_title(title)
        self.builder.add_paragraph(paragraph)

# 使用例
html_builder = HTMLDocumentBuilder()
director = Director(html_builder)
director.construct("Hello", "This is a sample document.")
print(html_builder.get_result())
# 出力:
# <h1>Hello</h1>
# <p>This is a sample document.</p>

md_builder = MarkdownDocumentBuilder()
director = Director(md_builder)
director.construct("Hello", "This is a sample document.")
print(md_builder.get_result())
# 出力:
# # Hello
# 
# This is a sample document.

この例では、HTMLとMarkdownの2つの異なるドキュメント形式を生成するためのビルダーを提供しています。Builder パターンを使用することで、新しいドキュメント形式を追加する際や、既存の形式の構築方法を変更する際に、Director クラスや他の部分のコードを変更することなく簡単に実現できます。

2.3.Factory Method パターン

Factory Method パターンは、インスタンスの生成をサブクラスに委譲するデザインパターンです。スーパークラス内でオブジェクトの生成に関する詳細を知る必要がなく、具体的なオブジェクトのクラスをサブクラスに隠蔽します。

例: ログの生成

  1. Logger という抽象クラスを定義し、create_log というファクトリメソッドを持つようにします。
  2. この抽象クラスに基づいて、ConsoleLoggerFileLogger という具体的なサブクラスを定義します。
  3. それぞれのサブクラスで、異なる種類のログオブジェクトを生成する create_log メソッドをオーバーライドします。

Pythonでの実装

from abc import ABC, abstractmethod

# Step 1: Log オブジェクトと Logger のインターフェースを定義
class Log(ABC):
    @abstractmethod
    def log_message(self, message: str):
        pass

class Logger(ABC):
    @abstractmethod
    def create_log(self) -> Log:
        pass

    def log(self, message: str):
        log_obj = self.create_log()
        log_obj.log_message(message)

# Step 2: 具体的なLogオブジェクトとLoggerを定義
class ConsoleLog(Log):
    def log_message(self, message: str):
        print(f"ConsoleLog: {message}")

class FileLog(Log):
    def log_message(self, message: str):
        with open('log.txt', 'a') as file:
            file.write(f"FileLog: {message}\n")

class ConsoleLogger(Logger):
    def create_log(self) -> Log:
        return ConsoleLog()

class FileLogger(Logger):
    def create_log(self) -> Log:
        return FileLog()

# 使用例
logger = ConsoleLogger()
logger.log("This is a message.")  # 出力: ConsoleLog: This is a message.

file_logger = FileLogger()
file_logger.log("This is a message.")  # log.txtに "FileLog: This is a message." が追加される

この例では、ログのメッセージをコンソールに出力するための ConsoleLogger と、ログファイルにメッセージを書き込むための FileLogger の2つのロガーを定義しています。Factory Method パターンを使用することで、新しいログの出力方法を追加する場合や、既存の出力方法を変更する場合でも、スーパークラスのコードを変更することなく実現できます。

2.4. Prototype パターン

Prototype パターンは、既存のオブジェクトをコピー(クローン)して新しいオブジェクトを作成するデザインパターンです。オブジェクトの具体的な型を知ることなく、オブジェクトを複製することができます。

例: 図形のクローン

  1. Shape という抽象クラスを定義し、clone というメソッドを持つようにします。
  2. この抽象クラスに基づいて、CircleRectangle という具体的なサブクラスを定義します。
  3. それぞれのサブクラスで、自身のオブジェクトをクローンする clone メソッドをオーバーライドします。

Pythonでの実装

import copy
from abc import ABC, abstractmethod

# Step 1: Shape オブジェクトとそのインターフェースを定義
class Shape(ABC):
    def __init__(self, color: str):
        self.color = color

    @abstractmethod
    def clone(self):
        pass

# Step 2: 具体的なShapeオブジェクトを定義
class Circle(Shape):
    def __init__(self, color: str, radius: float):
        super().__init__(color)
        self.radius = radius

    def clone(self):
        return copy.deepcopy(self)

    def __str__(self):
        return f"Circle: color={self.color}, radius={self.radius}"

class Rectangle(Shape):
    def __init__(self, color: str, width: float, height: float):
        super().__init__(color)
        self.width = width
        self.height = height

    def clone(self):
        return copy.deepcopy(self)

    def __str__(self):
        return f"Rectangle: color={self.color}, width={self.width}, height={self.height}"

# 使用例
circle1 = Circle("Red", 5)
circle2 = circle1.clone()
circle2.color = "Blue"
circle2.radius = 10

print(circle1)  # 出力: Circle: color=Red, radius=5
print(circle2)  # 出力: Circle: color=Blue, radius=10

この例では、CircleRectangle の2つの図形を定義しています。それぞれの図形には clone メソッドが実装されており、このメソッドを使用してオブジェクトのディープコピーを作成できます。このパターンの利点は、新しいインスタンスを効率的に作成できることや、オブジェクトの具体的な型を知ることなくクローンを作成できる点にあります。

2.5.Singleton パターン

Singleton パターンは、クラスのインスタンスが1つだけであることを保証するデザインパターンです。シングルトンクラスはその唯一のインスタンスを返すメソッドを持ち、直接インスタンス化することはできません。

例: 設定管理クラス

設定値を管理するクラスがあるとします。このクラスのインスタンスは1つだけ存在するべきであり、複数の場所からアクセスされても同じインスタンスを返すようにする必要があります。

Pythonでの実装

class Singleton:
    _instance = None

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

class ConfigurationManager(Singleton):
    def __init__(self):
        if not hasattr(self, "settings"):
            self.settings = {}

    def set_setting(self, key, value):
        self.settings[key] = value

    def get_setting(self, key):
        return self.settings.get(key)

# 使用例
config1 = ConfigurationManager()
config1.set_setting("api_key", "123456")

config2 = ConfigurationManager()
print(config2.get_setting("api_key"))  # 出力: 123456

print(config1 is config2)  # 出力: True

この例では、ConfigurationManager クラスは Singleton クラスを継承しており、このクラスのインスタンスが1つだけ存在することを保証しています。config1config2 は実際には同じインスタンスを参照しているため、config1 で設定した値は config2 からもアクセスできます。

シングルトンパターンは、グローバルな状態を持つ場合や、リソースが限られていて同時に複数のインスタンスを作成したくない場合などに有効です。ただし、グローバルな状態を持つことがプログラムの挙動を予測しづらくする場合もあるため、使用する際には注意が必要です。

3.構造に関するデザインパターンは

構造に関するデザインパターンは、オブジェクト間の関係をシンプルかつ効率的に構築するための方法を提供します。これらのパターンは、クラスやオブジェクトが共同で行動する方法を示し、その結果として大きな構造を構築するのを助けます。

以下は、構造に関するデザインパターンの一覧です:

Adapter(アダプター): 互換性のないインターフェースを持つクラスを、あるインターフェースに合わせて動作するように「変換」します。既存のクラスを新しいインターフェースに適合させるために使われます。

Bridge(ブリッジ): 実装から抽象を分離して、それらが独立して変化できるようにします。これにより、抽象と実装が個別に進化できます。

Composite(コンポジット): オブジェクトを木構造に組み合わせて、部分-全体の階層を表現します。これにより、クライアントは個別オブジェクトとオブジェクトのコンポジションを一貫して扱うことができます。

Decorator(デコレーター): オブジェクトに動的に新しい責任を追加します。サブクラス化の代わりに、より柔軟な拡張が可能となります。

Facade(ファサード): システムの特定のインターフェースをクライアントから別のインターフェースに向けて提供することで、システムの使用を簡単にします。

Flyweight(フライウェイト): 大量の似たようなオブジェクトを効率的に共有することで、メモリの使用量を最小限に抑えます。

Proxy(プロキシ): オブジェクトの代理として、他のオブジェクトへのアクセスを制御します。リモートプロキシ、仮想プロキシ、保護プロキシなど、多くの異なるタイプのプロキシが存在します。

これらのパターンは、特定の問題に対して特定の解決策を提供します。それぞれのパターンには独自の適用シナリオや利点がありますので、問題の内容や要件に応じて適切なものを選ぶことが重要です。

3.1.Adapter パターン

Adapter パターンは、既存のクラスのインターフェースを、期待される別のインターフェースに変換するデザインパターンです。これにより、インターフェースの互換性がないクラスを、問題なく一緒に動作させることができます。

例: 既存のクラスと新しいインターフェース

想像してみてください。ある古いシステムには、既存の OldPrinter というクラスがあり、このクラスは show メソッドを使用して情報を表示します。新しいシステムでは、print_data メソッドを持つ NewPrinterInterface という新しいインターフェースが期待されています。ここで、Adapter パターンを使って、OldPrinter を新しいインターフェースに適合させることができます。

Pythonでの実装

# 既存のクラス
class OldPrinter:
    def show(self, data):
        print(f"Showing data: {data}")

# 新しいインターフェース
class NewPrinterInterface:
    def print_data(self, data):
        raise NotImplementedError

# Adapter クラス
class PrinterAdapter(NewPrinterInterface):
    def __init__(self, old_printer):
        self.old_printer = old_printer

    def print_data(self, data):
        self.old_printer.show(data)

# 使用例
old_printer = OldPrinter()
adapter = PrinterAdapter(old_printer)
adapter.print_data("Hello, Adapter!")  # 出力: Showing data: Hello, Adapter!

この例では、PrinterAdapter クラスは NewPrinterInterface を実装していますが、内部で OldPrintershow メソッドを使っています。これにより、OldPrinter クラスが新しいインターフェースである NewPrinterInterface の要件を満たすようになりました。

Adapter パターンは、既存のコードを変更することなく新しいシステムやインターフェースと統合する必要がある場合に特に役立ちます。

3.2.Bridge パターン

Bridge パターンは、抽象化と実装を分離するデザインパターンです。このパターンの目的は、抽象化と実装が独立に変更できるようにすることです。Bridge パターンは、2つの階層のクラス(抽象化と実装)を持つことで、組み合わせの爆発を防ぐことができます。

例: シェイプと描画API

考えてみてください。シェイプのクラス(例えば、円や四角形)があり、それぞれのシェイプを異なる方法で描画したいとします(例えば、OpenGLやDirectXといった描画APIを使用して)。ここで、Bridge パターンを使用して、シェイプの抽象化と描画の実装を分離することができます。

Pythonでの実装

from abc import ABC, abstractmethod

# 描画APIの実装インターフェース
class DrawingAPI(ABC):
    @abstractmethod
    def draw_circle(self, x, y, radius):
        pass

# 具体的な描画APIの実装1
class OpenGLAPI(DrawingAPI):
    def draw_circle(self, x, y, radius):
        print(f"OpenGL drawing circle at {x}, {y} with radius {radius}")

# 具体的な描画APIの実装2
class DirectXAPI(DrawingAPI):
    def draw_circle(self, x, y, radius):
        print(f"DirectX drawing circle at {x}, {y} with radius {radius}")

# シェイプの抽象化
class Shape(ABC):
    def __init__(self, drawing_api):
        self._drawing_api = drawing_api

    @abstractmethod
    def draw(self):
        pass

# 具体的なシェイプの抽象化1
class Circle(Shape):
    def __init__(self, x, y, radius, drawing_api):
        super().__init__(drawing_api)
        self._x = x
        self._y = y
        self._radius = radius

    def draw(self):
        self._drawing_api.draw_circle(self._x, self._y, self._radius)

# 使用例
circle1 = Circle(1, 2, 3, OpenGLAPI())
circle1.draw()  # 出力: OpenGL drawing circle at 1, 2 with radius 3

circle2 = Circle(4, 5, 6, DirectXAPI())
circle2.draw()  # 出力: DirectX drawing circle at 4, 5 with radius 6

この例では、Shape は抽象化を表すクラスで、DrawingAPI は実装を表すインターフェースです。Circle クラスは Shape の具体的な実装であり、OpenGLAPIDirectXAPIDrawingAPI の具体的な実装です。Bridge パターンを使用することで、シェイプと描画APIの実装を独立に変更することができます。

3.3.Composite パターン

Composite パターンは、オブジェクトを木構造に組み合わせて、部分-全体の階層を表現するデザインパターンです。このパターンを使用すると、クライアントは個別オブジェクトとオブジェクトのコンポジションを一貫して扱うことができます。

例: グラフィックオブジェクト

考えてみてください。グラフィックオブジェクトのクラスがあり、その中には複数のグラフィックオブジェクトを含むグループもあるとします。この場合、Composite パターンを使用して、シングルオブジェクトとグループを同じインターフェースで扱うことができます。

Pythonでの実装

from abc import ABC, abstractmethod

# グラフィックオブジェクトのインターフェース
class Graphic(ABC):
    @abstractmethod
    def draw(self):
        pass

# シングルオブジェクト
class Circle(Graphic):
    def draw(self):
        print("Drawing Circle")

class Rectangle(Graphic):
    def draw(self):
        print("Drawing Rectangle")

# グラフィックのコンポジット(グループ)
class GraphicGroup(Graphic):
    def __init__(self):
        self._graphics = []

    def add(self, graphic):
        self._graphics.append(graphic)

    def draw(self):
        for graphic in self._graphics:
            graphic.draw()

# 使用例
circle1 = Circle()
rectangle1 = Rectangle()
group1 = GraphicGroup()
group1.add(circle1)
group1.add(rectangle1)

circle2 = Circle()
group2 = GraphicGroup()
group2.add(circle2)
group2.add(group1)

group2.draw()
# 出力:
# Drawing Circle
# Drawing Circle
# Drawing Rectangle

この例では、Graphic はシングルオブジェクト(Circle, Rectangle)とグループ(GraphicGroup)の共通のインターフェースです。GraphicGroup は複数の Graphic オブジェクト(シングルまたは他のグループ)を含むことができます。Composite パターンを使用することで、シングルオブジェクトとグループのオブジェクトを同じ方法で扱うことができます。

3.4.Decorator パターン

Decorator パターンは、オブジェクトに動的に新しい責任や機能を追加するためのデザインパターンです。このパターンは、サブクラスを使用することなく、オブジェクトの振る舞いを拡張する方法を提供します。

例: コーヒーの注文

考えてみてください。コーヒーを注文するシステムがあり、様々なトッピング(ミルク、シロップ、ホイップクリームなど)を追加することができるとします。Decorator パターンを使用すると、基本のコーヒーに動的にトッピングを追加することができます。

Pythonでの実装

from abc import ABC, abstractmethod

# コンポーネントのインターフェース
class Beverage(ABC):
    @abstractmethod
    def cost(self):
        pass

# 具体的なコンポーネント
class Coffee(Beverage):
    def cost(self):
        return 5  # コーヒーの基本料金

# デコレーターの基底クラス
class BeverageDecorator(Beverage):
    def __init__(self, beverage):
        self._beverage = beverage

# 具体的なデコレーター1
class MilkDecorator(BeverageDecorator):
    def cost(self):
        return self._beverage.cost() + 2  # ミルク料金追加

# 具体的なデコレーター2
class SyrupDecorator(BeverageDecorator):
    def cost(self):
        return self._beverage.cost() + 1  # シロップ料金追加

# 使用例
coffee = Coffee()
print(coffee.cost())  # 5

milk_coffee = MilkDecorator(coffee)
print(milk_coffee.cost())  # 7

syrup_milk_coffee = SyrupDecorator(milk_coffee)
print(syrup_milk_coffee.cost())  # 8

この例では、Coffee は基本的なコンポーネントであり、MilkDecoratorSyrupDecorator は具体的なデコレーターです。各デコレーターは、元のコンポーネント(または他のデコレーター)をラップして、追加の費用や機能を提供します。このように、Decorator パターンを使用すると、オブジェクトに動的に新しい機能を追加することができます。

3.5.Facade パターン

Facade パターンは、複雑なサブシステムのインターフェースを簡素化するデザインパターンです。このパターンは、クライアントとサブシステムの間にファサードという一つの統一されたインターフェースを提供し、サブシステムをより簡単にアクセスできるようにします。

例: コンピュータの起動

考えてみてください。コンピュータを起動するための一連の手順があるとします。これには、CPUの起動、メモリの初期化、ハードドライブの起動などのステップが含まれます。Facade パターンを使用すると、これらの手順を一つの簡単なメソッドで提供することができます。

Pythonでの実装

# サブシステム
class CPU:
    def start(self):
        print("Starting CPU...")

class Memory:
    def initialize(self):
        print("Initializing memory...")

class HardDrive:
    def boot(self):
        print("Booting hard drive...")

# ファサード
class Computer:
    def __init__(self):
        self.cpu = CPU()
        self.memory = Memory()
        self.hard_drive = HardDrive()

    def start_computer(self):
        self.cpu.start()
        self.memory.initialize()
        self.hard_drive.boot()
        print("Computer started!")

# 使用例
computer = Computer()
computer.start_computer()
# 出力:
# Starting CPU...
# Initializing memory...
# Booting hard drive...
# Computer started!

この例では、CPUMemoryHardDrive はサブシステムを表し、Computer はこれらのサブシステムを簡単にアクセスできるようにするファサードです。start_computer メソッドを使用することで、クライアントは複雑な内部の詳細を気にせずにコンピュータを起動することができます。Facade パターンを使用することで、サブシステムの使用を簡単にすることができます。

3.6.Flyweight パターン

Flyweight パターンは、多数の似たようなオブジェクトを効果的に共有してメモリ使用量を最小限に抑えるデザインパターンです。フライウェイトオブジェクトは、共有される不変の部分(内部状態)と、外部から与えられる変わる部分(外部状態)を区別します。

例: 文字レンダリング

考えてみてください。文章の各文字をレンダリングするシステムがあるとします。異なる文書や位置で同じ文字を何度もレンダリングする場合、それぞれの文字オブジェクトを毎回新しく生成するのは非効率的です。Flyweight パターンを使用すると、一度生成された文字オブジェクトを再利用することができます。

Pythonでの実装

class CharacterFlyweight:
    def __init__(self, character):
        self.character = character

    def render(self, fontsize):
        print(f"Rendering character {self.character} with font size {fontsize}")

class CharacterFactory:
    _characters = {}

    def get_character(self, character):
        if character not in self._characters:
            char_obj = CharacterFlyweight(character)
            self._characters[character] = char_obj
        return self._characters[character]

# 使用例
factory = CharacterFactory()

character_a1 = factory.get_character("A")
character_a2 = factory.get_character("A")

# これらは同じオブジェクトである
print(character_a1 is character_a2)  # True

character_a1.render(12)  # Rendering character A with font size 12
character_a2.render(14)  # Rendering character A with font size 14

この例では、CharacterFlyweight がフライウェイトオブジェクトを表しており、CharacterFactory はこれらのオブジェクトを生成および再利用するためのファクトリーです。同じ文字オブジェクトは一度だけ生成され、それが再利用されることでメモリの効率が向上します。文字のレンダリングの際にフォントサイズ(外部状態)を指定することで、同じオブジェクトを異なる状態で使用することができます。

3.7.Proxy パターン

Proxy パターンは、別のオブジェクトへのアクセスを制御するための代理オブジェクトを提供するデザインパターンです。Proxy パターンの使用理由は多岐にわたり、以下のようなケースが考えられます:

  1. Virtual Proxy: オブジェクトの生成が高コストである場合に、オブジェクトが実際に必要になるまでその生成を遅延させる。
  2. Protection Proxy: オブジェクトへのアクセスを制御する。たとえば、特定の条件下でのみオブジェクトへのアクセスを許可する。
  3. Remote Proxy: ネットワーク上の別の場所に存在するオブジェクトへのアクセスを表す。
  4. Cache Proxy: 高コストな操作の結果をキャッシュして、再利用する。

例: 画像の遅延読み込み (Virtual Proxy)

大きな画像ファイルを持つオブジェクトがあり、その読み込みを遅延させたい場合を考えます。

Pythonでの実装

class Image:
    def display(self):
        pass

class RealImage(Image):
    def __init__(self, filename):
        self.filename = filename
        self._load_from_disk()

    def _load_from_disk(self):
        print(f"Loading {self.filename}")

    def display(self):
        print(f"Displaying {self.filename}")

class ProxyImage(Image):
    def __init__(self, filename):
        self.filename = filename
        self.real_image = None

    def display(self):
        if not self.real_image:
            self.real_image = RealImage(self.filename)
        self.real_image.display()

# 使用例
image = ProxyImage("large_photo.jpg")

# この時点では画像はロードされていない
image.display()  # LoadingとDisplayingが行われる

# 2回目の表示では、既にロードされている画像を利用する
image.display()  # Loadingは行われず、Displayingのみ行われる

この例では、RealImage は実際の画像を読み込み表示するクラスで、ProxyImage は実際の画像の読み込みを遅延させるプロキシクラスです。初めて display メソッドが呼ばれるときにのみ画像が読み込まれ、2回目以降はキャッシュされたオブジェクトを利用します。

Proxy パターンを使用すると、オブジェクトへのアクセスを柔軟に制御することができます。

4. 振る舞いに関するパターン

振る舞いに関するパターン(Behavioral Patterns)は、オブジェクト間の責任の分配や通信を定義するデザインパターンを指します。これらのパターンは、特定の振る舞いやアルゴリズムの実装をオブジェクトの組み合わせで実現することを目指しています。

以下は、振る舞いに関する主なデザインパターンのリストと簡単な説明です:

Chain of Responsibility(責任の連鎖):

  • 複数のオブジェクトが要求を処理する機会を持つことを可能にし、その要求を受け取るオブジェクトと処理するオブジェクトの間の結合を避ける。

Command(コマンド):

  • 要求をオブジェクトとしてカプセル化し、異なる要求、キュー、またはログ要求とその操作をサポートすることで、パラメータを持つ手続きを汎用的にする。

Interpreter(インタープリター):

  • 与えられた言語の文法に対する解釈を表すための表現を定義し、この文法を使用して文を解釈するインタープリタを使用する。

Iterator(イテレーター):

  • 要素を順番にアクセスする方法を提供しながら、集約オブジェクトの実装を公開しない。

Mediator(メディエーター):

  • オブジェクト間の結合を減少させるために、オブジェクト間の通信をカプセル化する。

Memento(メメント):

  • オブジェクトの内部状態をキャプチャし、後でこの状態にオブジェクトを戻すことができるようにする。

Observer(オブザーバー):

  • オブジェクトの状態が変わったときに他のオブジェクトに自動的に通知する。

State(ステート):

  • オブジェクトの内部状態が変わったときに、オブジェクトの振る舞いを変更する。

Strategy(ストラテジー):

  • アルゴリズムを定義し、それぞれをカプセル化して、アルゴリズムの選択をクライアントができるようにする。

Template Method(テンプレートメソッド):

  • アルゴリズムの骨格を定義し、一部の手順をサブクラスで実装させる。

Visitor(ビジター):

  • クラスの操作を追加するためのもので、クラス自体を変更せずに新しい操作を追加する。

これらのデザインパターンは、特定の振る舞いや協力の問題を解決するためのベストプラクティスとして広く受け入れられています。適切なパターンを適切な場面で適用することで、ソフトウェアの設計がより柔軟で再利用可能になり、またメンテナンスも容易になります。

4.1. Chain of Responsibility パターン

Chain of Responsibility パターンは、複数のオブジェクトが要求を処理する機会を持つことを可能にするデザインパターンです。このパターンを使用すると、送信者と受信者の間の結合を避けることができます。また、要求を処理するオブジェクトのチェーン(鎖)を動的に構成することができます。

要求は、このチェーンの一端から開始され、次々とチェーンの各オブジェクトに渡されます。各オブジェクトは要求を処理するか、次のオブジェクトに渡すかの判断を行います。

Pythonでの実装例

以下は、Chain of Responsibility パターンの簡単な実装例です。この例では、ログメッセージを処理するシステムを考えます。異なるログレベル(エラー、警告、情報)のメッセージを処理する各ハンドラがあります。

class Handler:
    def __init__(self, successor=None):
        self.successor = successor

    def handle(self, message, level):
        if self.successor:
            self.successor.handle(message, level)

class ErrorHandler(Handler):
    ERROR = 3

    def handle(self, message, level):
        if level == self.ERROR:
            print(f"ErrorHandler: {message}")
        super().handle(message, level)

class WarningHandler(Handler):
    WARNING = 2

    def handle(self, message, level):
        if level == self.WARNING:
            print(f"WarningHandler: {message}")
        super().handle(message, level)

class InfoHandler(Handler):
    INFO = 1

    def handle(self, message, level):
        if level == self.INFO:
            print(f"InfoHandler: {message}")
        super().handle(message, level)

# 使用例
info_handler = InfoHandler()
warning_handler = WarningHandler(info_handler)
error_handler = ErrorHandler(warning_handler)

error_handler.handle("This is just an info.", 1)
error_handler.handle("This is a warning.", 2)
error_handler.handle("This is an error!", 3)

上記の例では、Handler クラスが基本となるハンドラを定義しています。各具体的なハンドラ(ErrorHandler, WarningHandler, InfoHandler)は Handler クラスを継承しています。要求は最初のハンドラに渡され、それが適切なハンドラかどうかを判断し、適切でない場合は次のハンドラに渡していきます。

このように、Chain of Responsibility パターンを使用すると、要求の処理のためのチェーンを柔軟に構築することができ、システムの拡張性も向上します。

4.2. Command パターン

Command パターンは、要求をオブジェクトとしてカプセル化するデザインパターンです。このパターンにより、異なる要求、キュー、またはログ要求とその操作をサポートすることで、操作のパラメータ化が可能となります。

具体的には、以下の利点があります:

  • 要求、キュー、ログの操作をデカップルする。
  • オペレーションをサポートするためのオペレーションの呼び出し、要求の変更、および要求の取り消しをサポートする。

Pythonでの実装例

以下は、簡単な音楽プレーヤーの操作をカプセル化するCommandパターンの例です。

from abc import ABC, abstractmethod

# Command抽象クラス
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass

# 具体的なCommandクラス
class PlayCommand(Command):
    def __init__(self, player):
        self.player = player

    def execute(self):
        self.player.play()

class StopCommand(Command):
    def __init__(self, player):
        self.player = player

    def execute(self):
        self.player.stop()

# Receiverクラス
class MusicPlayer:
    def play(self):
        print("Playing music...")

    def stop(self):
        print("Stopping music...")

# Invokerクラス
class RemoteController:
    def set_command(self, command):
        self.command = command

    def press_button(self):
        self.command.execute()

# 使用例
player = MusicPlayer()
play_command = PlayCommand(player)
stop_command = StopCommand(player)

remote = RemoteController()

remote.set_command(play_command)
remote.press_button()

remote.set_command(stop_command)
remote.press_button()

この例では、MusicPlayerはReceiverとして動作し、実際の操作(play, stop)を定義しています。PlayCommandおよびStopCommandは具体的なCommandとして定義され、それぞれの操作をカプセル化します。RemoteControllerはInvokerとして動作し、どのコマンドが実行されるかを決定します。

このようにCommandパターンを使用すると、Invoker(リモコン)とReceiver(音楽プレーヤー)の間の結合を緩和し、コマンドを容易に追加または変更することができます。

4.3. Interpreter パターン

Interpreter パターンは、ある言語の文法を表現するために使用されるパターンであり、その文法に基づいて文章を解釈するためのインタープリタを提供します。このパターンは、主に繰り返し発生するある種の問題を解決するための特定の「言語」を定義する必要がある場合に使用されます。

Pythonでの実装例

以下は、簡単な算術式を解釈するInterpreterの例です。

from abc import ABC, abstractmethod

# Abstract Expression
class Expression(ABC):
    @abstractmethod
    def interpret(self, context):
        pass

# Terminal Expression
class Integer(Expression):
    def __init__(self, value):
        self.value = value

    def interpret(self, context):
        return self.value

# Non-terminal Expression
class Plus(Expression):
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def interpret(self, context):
        return self.left.interpret(context) + self.right.interpret(context)

class Minus(Expression):
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def interpret(self, context):
        return self.left.interpret(context) - self.right.interpret(context)

# Context
class Context:
    pass

# 使用例
context = Context()
a = Integer(3)
b = Integer(5)

plus = Plus(a, b)
minus = Minus(a, b)

print(plus.interpret(context))  # Output: 8
print(minus.interpret(context)) # Output: -2

この例では、Expressionはすべての式の基本となる抽象クラスです。Integerは端末式(値そのもの)を、PlusおよびMinusは非端末式(演算子)を表現します。Contextは通常、外部情報を提供するために使用されますが、この例では単純化のために空のままとしています。

このように、Interpreter パターンを使用すると、特定の「言語」の文法を定義し、それに基づいて文章を解釈する能力を持ったオブジェクトを設計することができます。ただし、複雑な文法や大規模な文法になると、インタープリターの設計は複雑になりがちであるため、注意が必要です。

4.4. Iterator パターン

Iterator パターンは、コレクションの要素に順番にアクセスする方法を提供するデザインパターンです。このパターンの主な目的は、コレクションの内部表現を公開せずに、要素にアクセスし反復処理を行うための一貫したアクセス方法を提供することです。

Pythonでの実装例

Pythonの標準ライブラリにはイテレーターパターンが組み込まれており、__iter__()__next__()メソッドを持つオブジェクトはイテレータとして機能します。以下は、シンプルなカスタムイテレータの例です。

class SimpleRange:
    def __init__(self, limit):
        self.limit = limit
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.limit:
            raise StopIteration
        self.current += 1
        return self.current - 1

# 使用例
simple_range = SimpleRange(5)
for number in simple_range:
    print(number)

上記のSimpleRangeクラスは0から指定されたlimitの直前までの整数を返すシンプルなイテレータです。__iter__()メソッドはイテレータ自体を返し、__next__()メソッドは次の要素を返します。要素がない場合、StopIteration例外を送出します。

この実装を使用すると、forループなどのイテレーション構造を使用して、カスタムコレクションの要素に順番にアクセスすることができます。

Pythonの組み込み関数iter()next()も、このイテレーターパターンをサポートしています。そして、多くのPythonのコレクション型(リスト、タプル、辞書など)は、このイテレータープロトコルをサポートしているため、forループで直接使用できます。

4.5. Mediator パターン

Mediator パターンは、オブジェクト間の相互作用をカプセル化するオブジェクトを定義するデザインパターンです。このパターンの主な目的は、システム内のオブジェクト間の通信を中心化し、オブジェクト間の結合を緩和することです。これにより、オブジェクトは互いに直接通信することなく、メディエータを介して通信します。

Pythonでの実装例

以下は、チャットルームを模倣したMediatorパターンの簡単な実装例です。

from abc import ABC, abstractmethod

# Mediator抽象クラス
class Mediator(ABC):
    @abstractmethod
    def send_message(self, user, message):
        pass

# Concrete Mediator
class ChatRoom(Mediator):
    def __init__(self):
        self.users = []

    def add_user(self, user):
        self.users.append(user)

    def send_message(self, user, message):
        for u in self.users:
            if u != user:
                u.receive_message(user.name, message)

# Colleague抽象クラス
class User:
    def __init__(self, name, mediator):
        self.name = name
        self.mediator = mediator
        mediator.add_user(self)

    def send_message(self, message):
        self.mediator.send_message(self, message)

    def receive_message(self, from_user, message):
        print(f"{from_user} to {self.name}: {message}")

# 使用例
chatroom = ChatRoom()
alice = User("Alice", chatroom)
bob = User("Bob", chatroom)

alice.send_message("Hi Bob!")
bob.send_message("Hello Alice!")

この例では、ChatRoomがメディエータとして動作し、ユーザ間のメッセージの送受信を調整します。UserクラスはColleagueとして動作し、他のユーザとの間でメッセージの送受信を行いますが、その通信はChatRoomメディエータを介して行われます。

このように、Mediatorパターンを使用すると、オブジェクト間の相互作用を一元化し、オブジェクトが直接通信する必要をなくすことができます。これにより、システムの変更や拡張が容易になります。

4.6. Memento パターン

Memento パターンは、オブジェクトの内部状態をキャプチャして外部に保存し、後でこの状態にオブジェクトを戻すことができるようにするデザインパターンです。これにより、オブジェクトの状態の変更に関する機能(例:UndoやRestore)をサポートすることができます。

このパターンは、以下の3つの主要な役割から構成されます。

  1. Originator(発起者): 状態を持つオリジナルのオブジェクト。このオブジェクトの状態を保存したり、保存された状態から復元したりするための機能を持っています。
  2. Memento(メメント): Originatorの状態のスナップショットを保持するオブジェクト。外部のオブジェクト(Caretaker)に保存されますが、外部のオブジェクトが直接この状態にアクセスすることはできません。
  3. Caretaker(保持者): Mementoオブジェクトを保存するオブジェクト。Originatorの状態の履歴を保持することができますが、Mementoの内部状態に直接アクセスや変更をすることはできません。

Pythonでの実装例

class Memento:
    def __init__(self, state):
        self._state = state

    def get_saved_state(self):
        return self._state

class Originator:
    _state = ""

    def set(self, state):
        print(f"Originator: Setting state to {state}")
        self._state = state

    def save_to_memento(self):
        print("Originator: Saving to Memento.")
        return Memento(self._state)

    def restore_from_memento(self, memento):
        self._state = memento.get_saved_state()
        print(f"Originator: State after restoring from Memento: {self._state}")

class Caretaker:
    _saved_states = []

    def add_memento(self, memento):
        self._saved_states.append(memento)

    def get_memento(self, index):
        return self._saved_states[index]

# 使用例
caretaker = Caretaker()

originator = Originator()
originator.set("State1")
originator.set("State2")

# 状態を保存
caretaker.add_memento(originator.save_to_memento())

originator.set("State3")
caretaker.add_memento(originator.save_to_memento())

originator.set("State4")

# 状態を復元
originator.restore_from_memento(caretaker.get_memento(0))

上記の例では、Originatorクラスは状態をセットしたり、その状態をMementoに保存したり、Mementoから状態を復元したりするためのメソッドを持っています。Caretakerは、これらのメメントオブジェクトを保存するリストを持っており、必要に応じて特定のメメントを取得できるようになっています。

このように、Mementoパターンを使用すると、システムの特定の時点での状態をキャプチャし、後でその状態に戻すことができます。

4.7. Observer パターン

Observer パターンは、オブジェクトの状態が変更されたときに、その変更を登録されたオブザーバー(観察者)に自動的に通知するデザインパターンです。一つの被観察オブジェクトが多数のオブザーバーに変更を通知する場合などに使用されます。

主な役割は以下の通りです:

  1. Subject(被観察者): 複数のオブザーバーが観察するオブジェクト。オブザーバーの登録や解除、通知を行います。
  2. Observer(観察者): 被観察者の変更を監視するオブジェクト。被観察者から変更があったときに通知を受け取ります。

Pythonでの実装例

from abc import ABC, abstractmethod

# Observerインターフェース
class Observer(ABC):
    @abstractmethod
    def update(self, message: str):
        pass

# ConcreteObserver
class ConcreteObserver(Observer):
    def __init__(self, name: str):
        self._name = name

    def update(self, message: str):
        print(f"Observer {self._name} received message: {message}")

# Subjectインターフェース
class Subject(ABC):
    @abstractmethod
    def add_observer(self, observer: Observer):
        pass

    @abstractmethod
    def remove_observer(self, observer: Observer):
        pass

    @abstractmethod
    def notify_observers(self):
        pass

# ConcreteSubject
class ConcreteSubject(Subject):
    def __init__(self):
        self._observers = []
        self._message = ""

    def add_observer(self, observer: Observer):
        self._observers.append(observer)

    def remove_observer(self, observer: Observer):
        self._observers.remove(observer)

    def notify_observers(self):
        for observer in self._observers:
            observer.update(self._message)

    def set_message(self, message: str):
        self._message = message
        self.notify_observers()

# 使用例
subject = ConcreteSubject()

# オブザーバーを作成し、被観察者に登録
observer1 = ConcreteObserver("Observer 1")
observer2 = ConcreteObserver("Observer 2")
subject.add_observer(observer1)
subject.add_observer(observer2)

# メッセージを設定
subject.set_message("First message")

# オブザーバーを削除
subject.remove_observer(observer2)

# 再度メッセージを設定
subject.set_message("Second message")

この例では、ConcreteSubjectがメッセージの変更を監視される被観察者として機能し、ConcreteObserverがそれを観察するオブザーバーとして機能します。メッセージがConcreteSubjectで設定されると、すぐにその変更がすべての登録されたオブザーバーに通知されます。

Observerパターンを使用すると、一つのオブジェクトの変更を多数のオブジェクトに簡単に伝えることができ、オブジェクト間の結合を低く保ちながら情報を共有することができます。

4.8. State パターン

State パターンは、オブジェクトの内部状態が変わることでその動作を変更するデザインパターンです。これにより、オブジェクトは状態ごとに異なる振る舞いを持つことができます。State パターンは、多くの条件文を持つコードを避け、オブジェクトの状態を変更することで動作を変える方法を提供します。

主な役割は以下の通りです:

  1. Context(コンテキスト): 現在のステートを保持し、このステートに基づいて振る舞います。
  2. State(ステート): すべての具体的なステートが継承または実装するインターフェースや抽象クラスです。
  3. ConcreteState(具体的なステート): State インターフェース/抽象クラスを実装した具体的な状態を示すクラスです。

Pythonでの実装例

from abc import ABC, abstractmethod

# Stateインターフェース
class State(ABC):
    @abstractmethod
    def handle_request(self, context):
        pass

# ConcreteStateA
class ConcreteStateA(State):
    def handle_request(self, context):
        print("State A handling request.")
        context.set_state(ConcreteStateB())

# ConcreteStateB
class ConcreteStateB(State):
    def handle_request(self, context):
        print("State B handling request.")
        context.set_state(ConcreteStateA())

# Context
class Context:
    def __init__(self, state: State):
        self._state = state

    def set_state(self, state: State):
        self._state = state

    def request(self):
        self._state.handle_request(self)

# 使用例
context = Context(ConcreteStateA())
context.request()  # "State A handling request."
context.request()  # "State B handling request."
context.request()  # "State A handling request."

この例では、Contextクラスは現在のステート(ConcreteStateAまたはConcreteStateB)を保持しています。requestメソッドが呼び出されると、現在のステートのhandle_requestメソッドが実行され、その結果としてステートが切り替わります。

State パターンを使用すると、コンテキストの動作を綺麗に分離して管理することができ、状態の変更が新しい状態への遷移を明確に示すことができます。

4.9. Strategy パターン

Strategy パターンは、アルゴリズムのセットを定義し、それぞれをカプセル化して、互換性のある方法でそれらを交換できるようにします。Strategy パターンを使用すると、アルゴリズムを使用するクライアントから独立してアルゴリズムを選択できるようになります。

主な役割は以下の通りです:

  1. Strategy(ストラテジー): すべての具体的なストラテジーが実装するインターフェースや抽象クラスです。
  2. ConcreteStrategy(具体的なストラテジー): Strategy インターフェース/抽象クラスを実装した具体的なアルゴリズムまたは動作を示すクラスです。
  3. Context(コンテキスト): 現在のストラテジーを保持し、このストラテジーに基づいて振る舞います。

Pythonでの実装例

from abc import ABC, abstractmethod

# Strategyインターフェース
class SortingStrategy(ABC):
    @abstractmethod
    def sort(self, data: list) -> list:
        pass

# ConcreteStrategyA
class BubbleSortStrategy(SortingStrategy):
    def sort(self, data: list) -> list:
        # バブルソートの実装
        n = len(data)
        for i in range(n):
            for j in range(0, n-i-1):
                if data[j] > data[j+1]:
                    data[j], data[j+1] = data[j+1], data[j]
        return data

# ConcreteStrategyB
class QuickSortStrategy(SortingStrategy):
    def sort(self, data: list) -> list:
        if len(data) <= 1:
            return data
        pivot = data[0]
        left, right, mid = [], [], [pivot]
        for x in data[1:]:
            if x < pivot:
                left.append(x)
            elif x > pivot:
                right.append(x)
            else:
                mid.append(x)
        return self.sort(left) + mid + self.sort(right)

# Context
class SortContext:
    def __init__(self, strategy: SortingStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: SortingStrategy):
        self._strategy = strategy

    def execute_sort(self, data: list) -> list:
        return self._strategy.sort(data)

# 使用例
data = [64, 34, 25, 12, 22, 11, 90]

context = SortContext(BubbleSortStrategy())
print(context.execute_sort(data.copy()))  # バブルソートでソート

context.set_strategy(QuickSortStrategy())
print(context.execute_sort(data.copy()))  # クイックソートでソート

この例では、SortContextクラスはソートアルゴリズム(ストラテジー)を保持しており、execute_sortメソッドを通じてソートを実行します。異なるソートアルゴリズムを簡単に切り替えることができます。

Strategy パターンを使用すると、アルゴリズムや動作のバリエーションをクラスの階層構造を使わずに持つことができ、動的にアルゴリズムや動作を切り替えることが可能になります。

4.10. Template Method パターン

Template Method パターンは、アルゴリズムの骨格を定義するメソッドを持つ抽象クラスを使用して、アルゴリズムの一部のステップをサブクラスに委譲するデザインパターンです。これにより、アルゴリズムの構造を変更せずに特定のステップをサブクラスで実装することができます。

主な役割は以下の通りです:

  1. AbstractClass(抽象クラス): アルゴリズムの骨格となるテンプレートメソッドを定義します。このメソッドは、いくつかの抽象メソッドや具体メソッドを呼び出すことで構成されます。
  2. ConcreteClass(具体クラス): 抽象クラスで定義された抽象メソッドを実装します。

Pythonでの実装例

from abc import ABC, abstractmethod

# AbstractClass
class AbstractCookingProcess(ABC):
    def prepare_dish(self):
        self.gather_ingredients()
        self.cook()
        self.serve()

    @abstractmethod
    def gather_ingredients(self):
        pass

    @abstractmethod
    def cook(self):
        pass

    def serve(self):
        print("Dish is served!")

# ConcreteClassA
class PastaCooking(AbstractCookingProcess):
    def gather_ingredients(self):
        print("Gathering pasta, tomatoes, and cheese.")

    def cook(self):
        print("Boiling pasta and cooking sauce.")

# ConcreteClassB
class SoupCooking(AbstractCookingProcess):
    def gather_ingredients(self):
        print("Gathering vegetables, broth, and spices.")

    def cook(self):
        print("Boiling and simmering the ingredients.")

# 使用例
pasta = PastaCooking()
pasta.prepare_dish()
# Gathering pasta, tomatoes, and cheese.
# Boiling pasta and cooking sauce.
# Dish is served!

soup = SoupCooking()
soup.prepare_dish()
# Gathering vegetables, broth, and spices.
# Boiling and simmering the ingredients.
# Dish is served!

この例では、AbstractCookingProcessクラスがテンプレートメソッドprepare_dishを持ち、料理の一般的な手順を定義しています。具体的な手順(材料の収集や調理方法)は、具体クラスであるPastaCookingSoupCookingで実装されています。

Template Method パターンを使用すると、アルゴリズムの一部をサブクラスで簡単にカスタマイズすることができ、コードの重複を避けることができます。

4.11. Visitor パターン

Visitor パターンは、オブジェクト構造の要素に対して新しい操作を追加することを容易にするためのデザインパターンです。これにより、オブジェクト構造のクラスを変更せずに新しい操作を追加することができます。

主な役割は以下の通りです:

  1. Visitor: 操作を定義するインターフェースです。
  2. ConcreteVisitor: 具体的な操作を実装するクラスです。
  3. Element: 要素を表すインターフェースで、accept メソッドを持ちます。
  4. ConcreteElement: 実際の要素を表すクラスです。

Pythonでの実装例

from abc import ABC, abstractmethod

# Element インターフェース
class Element(ABC):
    @abstractmethod
    def accept(self, visitor):
        pass

# ConcreteElementA
class Book(Element):
    def accept(self, visitor):
        visitor.visit_book(self)

    def get_title(self):
        return "Design Patterns"

# ConcreteElementB
class Author(Element):
    def accept(self, visitor):
        visitor.visit_author(self)

    def get_name(self):
        return "Erich Gamma"

# Visitor インターフェース
class Visitor(ABC):
    @abstractmethod
    def visit_book(self, book):
        pass

    @abstractmethod
    def visit_author(self, author):
        pass

# ConcreteVisitor
class PrintVisitor(Visitor):
    def visit_book(self, book):
        print("Book title:", book.get_title())

    def visit_author(self, author):
        print("Author name:", author.get_name())

# 使用例
book = Book()
author = Author()
visitor = PrintVisitor()

book.accept(visitor)   # Book title: Design Patterns
author.accept(visitor) # Author name: Erich Gamma

この例では、BookAuthorクラスがElementを実装しており、それぞれの情報を取得するメソッドを持っています。PrintVisitorは、これらの要素を訪れて情報を表示します。

Visitor パターンは、オブジェクト構造の要素に対する操作を独立させることができるため、要素のクラスを変更せずに新しい操作を追加する際に役立ちます。

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?