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スタイル(LightTheme
と DarkTheme
)のボタンとメニューを考えます。
-
Button
とMenu
というインターフェースを持つクラスを定義します。 - これらのインターフェースに基づいて、
LightThemeButton
,DarkThemeButton
,LightThemeMenu
,DarkThemeMenu
という具体的なクラスを定義します。 - これらのオブジェクトを生成するための抽象工場(
AbstractUIFactory
)を定義します。 -
LightThemeUIFactory
とDarkThemeUIFactory
という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ドキュメントを生成するビルダーの例です。
-
DocumentBuilder
という抽象ビルダークラスを定義します。 - この抽象ビルダーに基づいて、
HTMLDocumentBuilder
とMarkdownDocumentBuilder
という具体的なビルダークラスを定義します。 -
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 パターンは、インスタンスの生成をサブクラスに委譲するデザインパターンです。スーパークラス内でオブジェクトの生成に関する詳細を知る必要がなく、具体的なオブジェクトのクラスをサブクラスに隠蔽します。
例: ログの生成
-
Logger
という抽象クラスを定義し、create_log
というファクトリメソッドを持つようにします。 - この抽象クラスに基づいて、
ConsoleLogger
とFileLogger
という具体的なサブクラスを定義します。 - それぞれのサブクラスで、異なる種類のログオブジェクトを生成する
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 パターンは、既存のオブジェクトをコピー(クローン)して新しいオブジェクトを作成するデザインパターンです。オブジェクトの具体的な型を知ることなく、オブジェクトを複製することができます。
例: 図形のクローン
-
Shape
という抽象クラスを定義し、clone
というメソッドを持つようにします。 - この抽象クラスに基づいて、
Circle
とRectangle
という具体的なサブクラスを定義します。 - それぞれのサブクラスで、自身のオブジェクトをクローンする
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
この例では、Circle
と Rectangle
の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つだけ存在することを保証しています。config1
と config2
は実際には同じインスタンスを参照しているため、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
を実装していますが、内部で OldPrinter
の show
メソッドを使っています。これにより、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
の具体的な実装であり、OpenGLAPI
と DirectXAPI
は DrawingAPI
の具体的な実装です。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
は基本的なコンポーネントであり、MilkDecorator
と SyrupDecorator
は具体的なデコレーターです。各デコレーターは、元のコンポーネント(または他のデコレーター)をラップして、追加の費用や機能を提供します。このように、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!
この例では、CPU
、Memory
、HardDrive
はサブシステムを表し、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 パターンの使用理由は多岐にわたり、以下のようなケースが考えられます:
- Virtual Proxy: オブジェクトの生成が高コストである場合に、オブジェクトが実際に必要になるまでその生成を遅延させる。
- Protection Proxy: オブジェクトへのアクセスを制御する。たとえば、特定の条件下でのみオブジェクトへのアクセスを許可する。
- Remote Proxy: ネットワーク上の別の場所に存在するオブジェクトへのアクセスを表す。
- 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つの主要な役割から構成されます。
- Originator(発起者): 状態を持つオリジナルのオブジェクト。このオブジェクトの状態を保存したり、保存された状態から復元したりするための機能を持っています。
- Memento(メメント): Originatorの状態のスナップショットを保持するオブジェクト。外部のオブジェクト(Caretaker)に保存されますが、外部のオブジェクトが直接この状態にアクセスすることはできません。
- 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 パターンは、オブジェクトの状態が変更されたときに、その変更を登録されたオブザーバー(観察者)に自動的に通知するデザインパターンです。一つの被観察オブジェクトが多数のオブザーバーに変更を通知する場合などに使用されます。
主な役割は以下の通りです:
- Subject(被観察者): 複数のオブザーバーが観察するオブジェクト。オブザーバーの登録や解除、通知を行います。
- 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 パターンは、多くの条件文を持つコードを避け、オブジェクトの状態を変更することで動作を変える方法を提供します。
主な役割は以下の通りです:
- Context(コンテキスト): 現在のステートを保持し、このステートに基づいて振る舞います。
- State(ステート): すべての具体的なステートが継承または実装するインターフェースや抽象クラスです。
-
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 パターンを使用すると、アルゴリズムを使用するクライアントから独立してアルゴリズムを選択できるようになります。
主な役割は以下の通りです:
- Strategy(ストラテジー): すべての具体的なストラテジーが実装するインターフェースや抽象クラスです。
-
ConcreteStrategy(具体的なストラテジー):
Strategy
インターフェース/抽象クラスを実装した具体的なアルゴリズムまたは動作を示すクラスです。 - 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 パターンは、アルゴリズムの骨格を定義するメソッドを持つ抽象クラスを使用して、アルゴリズムの一部のステップをサブクラスに委譲するデザインパターンです。これにより、アルゴリズムの構造を変更せずに特定のステップをサブクラスで実装することができます。
主な役割は以下の通りです:
- AbstractClass(抽象クラス): アルゴリズムの骨格となるテンプレートメソッドを定義します。このメソッドは、いくつかの抽象メソッドや具体メソッドを呼び出すことで構成されます。
- 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
を持ち、料理の一般的な手順を定義しています。具体的な手順(材料の収集や調理方法)は、具体クラスであるPastaCooking
とSoupCooking
で実装されています。
Template Method パターンを使用すると、アルゴリズムの一部をサブクラスで簡単にカスタマイズすることができ、コードの重複を避けることができます。
4.11. Visitor パターン
Visitor パターンは、オブジェクト構造の要素に対して新しい操作を追加することを容易にするためのデザインパターンです。これにより、オブジェクト構造のクラスを変更せずに新しい操作を追加することができます。
主な役割は以下の通りです:
- Visitor: 操作を定義するインターフェースです。
- ConcreteVisitor: 具体的な操作を実装するクラスです。
-
Element: 要素を表すインターフェースで、
accept
メソッドを持ちます。 - 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
この例では、Book
とAuthor
クラスがElement
を実装しており、それぞれの情報を取得するメソッドを持っています。PrintVisitor
は、これらの要素を訪れて情報を表示します。
Visitor パターンは、オブジェクト構造の要素に対する操作を独立させることができるため、要素のクラスを変更せずに新しい操作を追加する際に役立ちます。