この記事では、Pythonで実装されたFactory Method Design Patternを紹介します。
デザインパターンは、ソフトウェア開発において繰り返し発生するさまざまな問題に対する、試行錯誤の末の解決策を定義したものです。
実際のコードではなく、最適な結果を得るためにコードを整理する方法を示しています。
リソースが限られた世界では、デザインパターンは最小限のリソースで最大の成果を上げるのに役立つ。
また、デザインパターンはすべての状況に適用できるわけではなく、特定のシナリオに最適なアプローチを選択するためには、目の前の問題を評価することが重要であることに注意する必要がある。
デザインパターンはいくつかの大きなカテゴリーに分けられるが、主にCreational Patterns、Structural Patterns、Behavioral Patternsに分けられる。
Factory MethodパターンはCreational Design Patternの1つである。
ファクトリーメソッドデザインパターン
定義
ファクトリーメソッドは、オブジェクト指向プログラミングにおいて、オブジェクトを作成するためのファクトリーインターフェースを提供する手段として使用されます。
これらのインターフェースは、一般的な構造を定義しますが、オブジェクトを初期化することはありません。
初期化は、より具体的なサブクラスに任されます。
親クラス/インターフェースは、異なるタイプのサブクラス間で共有できる標準的・汎用的な振る舞いをすべて収容しています。
サブクラスは順に、スーパークラスに基づいてオブジェクトの定義とインスタンス化を担当します。
モチベーション
ファクトリーメソッドデザインパターンの主な動機は、いくつかの共通の属性と機能を共有する異なるタイプのオブジェクトを作成するために使用される抽象クラスの作成を通じて、コード内の疎結合を強化することです。
その結果、同じクラスから継承された共有機能が書き直されることがないため、コードの柔軟性と再利用性が向上する。
このデザインパターンは、仮想コンストラクタとしても知られています。
ファクトリーメソッドのデザインパターンは、抽象クラスを通して、クライアントがどのサブクラスまたはタイプのオブジェクトを作成するかを選択できるようにすることで、一般的にライブラリで使用されています。
ファクトリーメソッドは、必要なオブジェクトに関する情報を受け取り、それをインスタンス化し、指定された型のオブジェクトを返します。
これにより、アプリケーションやライブラリは、他のプログラムやコードとやり取りするポイントを1つにし、オブジェクトの生成機能をカプセル化することができる。
ファクトリーメソッドの実装
本プログラムは、図形オブジェクトの生成や色付け、面積計算などの操作を行うためのライブラリになります。
ユーザは、このライブラリを使って新しいオブジェクトを作成することができるはずです。
しかし、その場合、多くの共有ロジックをそれぞれのシェイプごとに書き直さなければなりません。
この繰り返しを解決する最初のステップは、calculate_area()
や calculate_perimeter()
などのメソッドと、寸法などのプロパティを持つ親 Shape クラスを作成することでしょう。
そして、特定のShapeオブジェクトは親クラスを継承することになります。
形状を作成するためには、どのような形状が必要かを特定し、そのためのサブクラスを作成する必要があります。
まず、一般的な図形を表現する抽象クラスを作成することから始めます。
import abc
class Shape(metaclass=abc.ABCMeta):
@abc.abstractmethod
def calculate_area(self):
pass
@abc.abstractmethod
def calculate_perimeter(self):
pass
これは、すべての図形の基底クラスとなります。
次に、より具体的な図形をいくつか作成します。
class Rectangle(Shape):
def __init__(self, height, width):
self.height = height
self.width = width
def calculate_area(self):
return self.height * self.width
def calculate_perimeter(self):
return 2 * (self.height + self.width)
class Square(Shape):
def __init__(self, width):
self.width = width
def calculate_area(self):
return self.width ** 2
def calculate_perimeter(self):
return 4 * self.width
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def calculate_area(self):
return 3.14 * self.radius * self.radius
def calculate_perimeter(self):
return 2 * 3.14 * self.radius
ここまでで、抽象的なクラスを作成し、ライブラリで利用できるさまざまな形状に合うように拡張してきました。
異なる形状のオブジェクトを作成するために、クライアントは形状の名前と詳細を知って、別途作成を行う必要があります。
ここで、ファクトリーメソッドの出番です。
つまり、クライアントは利用可能な形状をすべて知っている必要はなく、実行時に必要なものだけを作成することができます。
また、オブジェクトの生成を一元化し、カプセル化することができる。
クライアントの入力に基づき特定のシェイプクラスを作成するために使用される ShapeFactory
を作成することでこれを実現しましょう。
class ShapeFactory:
def create_shape(self, name):
if name == 'circle':
radius = input("Enter the radius of the circle: ")
return Circle(float(radius))
elif name == 'rectangle':
height = input("Enter the height of the rectangle: ")
width = input("Enter the width of the rectangle: ")
return Rectangle(int(height), int(width))
elif name == 'square':
width = input("Enter the width of the square: ")
return Square(int(width))
これはオブジェクト生成のためのインターフェイスです。
具体的なクラスのコンストラクタを呼び出すのではなく、ファクトリーを呼び出して形状を作成するように依頼します。
私たちの ShapeFactory
は、名前や必要な寸法など、シェイプに関する情報を受け取ることで動作します。
ファクトリーのメソッド create_shape()
は、希望する形状のオブジェクトを作成して返すために使用されます。
クライアントはオブジェクトの生成や仕様について何も知る必要はありません。
ファクトリーオブジェクトを使うことで、最小限の知識でオブジェクトを作成することができるのです。
def shapes_client():
shape_factory = ShapeFactory()
shape_name = input("Enter the name of the shape: ")
shape = shape_factory.create_shape(shape_name)
print(f"The type of object created: {type(shape)}")
print(f"The area of the {shape_name} is: {shape.calculate_area()}")
print(f"The perimeter of the {shape_name} is: {shape.calculate_perimeter()}")
このコードを実行すると、次のような結果になります。
Enter the name of the shape: circle
Enter the radius of the circle: 7
The type of object created: <class '__main__.circle'=""
The area of the circle is: 153.86
The perimeter of the circle is: 43.96
あるいは、別の形状を作ることもできる。
Enter the name of the shape: square
Enter the width of the square: 5
The type of object created: <class '__main__.square'=""
The area of the square is: 25
The perimeter of the square is: 20
注目すべきは、クライアントが作成プロセスについてあまり知らなくてもよいということです。
オブジェクトをインスタンス化したいとき、私たちはクラスのコンストラクタを呼び出すことはありません。
オブジェクトのインスタンスを作成したいとき、私たちはクラスのコンストラクタを呼び出すことはありません。
私たちは create_shape()
関数に渡した情報に基づいて、ファクトリーにこれを行うように依頼します。
長所と短所
プロフェッショナル
ファクトリーメソッドのデザインパターンを使用する大きな利点の1つは、コードが疎結合になり、コードのコンポーネントの大部分が同じコードベースの他のコンポーネントを意識することがなくなることです。
その結果、理解やテストが容易なコードになり、プログラム全体に影響を与えたり壊したりすることなく、特定のコンポーネントに機能を追加することができます。
また、ファクトリーメソッドのデザインパターンは、特定の機能を扱うクラスやオブジェクトがより良いコードを生み出すという単一責任原則を支持するのに役立ちます。
短所
クラスが増えると、可読性が低下する。
抽象ファクトリー(ファクトリーのファクトリー)と組み合わせると、保守性は高いが、すぐに冗長なコードになってしまう。
結論
結論として、ファクトリーメソッドデザインパターンは、特定のオブジェクトを作成するために必要な正確なクラスを指定せずにオブジェクトを作成することを可能にします。
これにより、コードを分離し、再利用性を向上させることができる。
ただし、他のデザインパターンと同様に、特定の状況にのみ適しており、すべての開発シナリオに適しているわけではないことに注意する必要がある。
ファクトリーメソッドデザインパターンの恩恵を受けるために実装を決定する前に、目下の状況を評価することは非常に重要である。