アンチパターンは、その名が示している通り、ソフトウェアパターン (software pattern) の反意語です。また、ソフトウェア開発において日常的に出現する「何か」でもあります。それは、ある問題を解決する、もしくは、回避するための一般的な道筋を示すものではありますが、大体において「誤った」道筋を提示するものです。だからと言って、デザインパターン (design pattern) が常に「正しい」道筋を指し示す、と言っているわけではありません。ここで言わんとしているのは、我々の目標に対してどれだけ「役に立つか」ということです。そして「我々の目標」というのは、デバッグ/更新/拡張が容易な「クリーン」なコードを記述する、ことです。この点において、アンチパターンは「役に立たず」、目標とは正反対の bad code の生産を助長するものなのです。
Telescoping constructor anti-pattern の問題点は、それぞれ特定数のパラメータを受け取る複数のコンストラクタが存在し、クラスが適切に実装されているのならば、それぞれのコンストラクタがその大元の部分でデフォルトのコンストラクタに依存している、ということです。
ビルダーパターン (builder pattern) では多数のコンストラクタを記述することはありません; その代わりビルダーオブジェクト (builder object) を利用します。Builder object では初期化のためのパラメータを段階的に受け取り、結果的にただ1つのオブジェクトを作成し返します。我々の webform 生成プログラムの例でいえば、異なるフィールドを順次追加し、その結果として1つの webform を受け取りたいわけです。
こうしたビルダーパターンの処理の利点は、結果としてのオブジェクトが表現する全体 (webform) と、そのオブジェクトを構成する方法 (どのようなフィールドをどのような順番で配置するか) を分離することができる、というものです。ですから、生成プロセスを変更することなく最終的なオブジェクトが「表現するもの」を変えることが可能になります。
ビルダーパターンにおいて中心的な役割を果たすのが Builder と Director です。
Builder は抽象クラス (abstract class) であり、最終的な生成物であるオブジェクトを構成する全てのコンポーネントの構築方法を把握しているものです。我々の例でいえば、webform を構成する全てのフィールドタイプの構築方法、ということになります。
一方 Director は、各コンポーネントの「組み立て方法」をコントロールするものです。つまり、1つ以上の Builder インスタンスから最終オブジェクト (我々の例では webform) を作り上げるわけです。Director では、フィールドを組み合わせて1つのオブジェクトを生成するための「一連の手順」を実装します。この「一連の手順」は、渡されてくる個々のフィールドタイプには左右されません。
Python におけるビルダーパターンのジェネリックな実装方法は次のようになります:
from abc import ABCMeta, abstractmethod
class Director(metaclass=ABCMeta):
def __init__(self):
self._builder = None
@abstractmethod
def construct(self):
pass
def get_constructed_object(self):
return self._builder.constructed_object
class Builder(metaclass=ABCMeta):
def __init__(self, constructed_object):
self.constructed_object = constructed_object
class Product:
def __init__(self):
pass
def __repr__(self):
pass
class ConcreteBuilder(Builder):
pass
class ConcreteDirector(Director):
pass
ご覧のように、Builder 抽象クラスはオブジェクトを構成するコンポーネントクラスのためのインターフェースを提供し、ConcreteBuilder 具象クラスがこれを実装します。これがインスタンス化されたオブジェクトは他のオブジェクト生成に利用することが可能です。
Builder pattern を採用した我々の webform 生成プログラムは以下のようになるでしょう:
form_builder_implementation.py
from abc import ABCMeta, abstractmethod
class HtmlForm:
def __init__(self):
self.field_list: list[str] = []
def __repr__(self) -> str:
return f'<form>{"".join(self.field_list)}</form>'
class AbstractFormBuilder(metaclass=ABCMeta):
def __init__(self):
self.constructed_object: HtmlForm = None
@abstractmethod
def add_text_field(self, field_dict: dict[str, str]):
pass
@abstractmethod
def add_checkbox(self, checkbox_dict: dict[str, str]):
pass
@abstractmethod
def add_button(self, button_dict: dict[str, str]):
pass
class Director(metaclass=ABCMeta):
def __init__(self):
self._builder: AbstractFormBuilder = None
def set_builder(self, builder: AbstractFormBuilder):
self._builder = builder
@abstractmethod
def construct(self, field_list: list[dict[str, str]]):
pass
def get_constructed_object(self) -> HtmlForm:
return self._builder.constructed_object
class HtmlFormBuilder(AbstractFormBuilder):
def __init__(self):
self.constructed_object = HtmlForm()
def add_text_field(self, field_dict):
self.constructed_object.field_list.append(
f
)
def add_checkbox(self, checkbox_dict):
self.constructed_object.field_list.append(
f
)
def add_button(self, button_dict):
self.constructed_object.field_list.append(
f"<button type='button'>{button_dict['text']}</button><br>"
)
class FormDirector(Director):
def __init__(self):
super().__init__()
def construct(self, field_list):
for field in field_list:
if field['field_type'] == 'text_field':
self._builder.add_text_field(field)
elif field['field_type'] == 'checkbox':
self._builder.add_checkbox(field)
elif field['field_type'] == 'button':
self._builder.add_button(field)
if __name__ == '__main__':
director = FormDirector()
html_form_builder = HtmlFormBuilder()
director.set_builder(html_form_builder)
field_list = [
{
'field_type': 'text_field',
'label': '今までで最高の文章!',
'field_name': 'best_text',
},
{
'field_type': 'checkbox',
'field_id': 'check_it',
'value': '1',
'label': '1 ならチェック!',
},
{
'field_type': 'text_field',
'label': 'もう1つテキストフィールド',
'field_name': 'text_field2',
},
{
'field_type': 'button',
'text': 'DONE',
}
]
director.construct(field_list)
with open('form_file.html', mode='w', encoding='utf-8') as f:
f.write(
f'<html><body>{director.get_constructed_object()}</body></html>'
)
ここで記述したスクリプトでは、テキストフィールド、チェックボックス、ボタンの3タイプだけを処理しています。どうぞ練習のために他のフィールドタイプも付け加えてみてください。
webform の「実体の作成」に必要なコードは Builder 具象クラス (concrete class) に記述されています。Director はその作成コードを呼び出します。これによって、様々な製品 (この例では webform) を生成するためのロジックは抽象化されることになります。
このコードを追ってみれば、builder pattern が最終的な webform を作成するために如何に1ステップ1ステップを積み上げていっているかが分かると思います。この動作は、ポリモーフィズムを利用して関連するグループオブジェクトをまとめて作り上げて一気に「ドンッ」と返す abstract factory (抽象ファクトリー) と明らかな対照をなしています。
ビルダーパターンは、複雑なオブジェクトの構築とその最終オブジェクトが全体として形容するものを切り離すため、同じ構築プロセスを利用して単純なテキストフィールドからネイティブアプリのインターフェースまで、様々なものを表現するフォームを作成することができます。
また、こういったコードの「分離」は、オブジェクトのサイズを小さくしよりクリーンなコードへ導く、という副次的な効果ももたらします。構築プロセスの見通しが良くなることで、オブジェクト作成過程に対する変更を容易に行えるようになります。
逆に、builder pattern の最大の短所は、作成したいオブジェクト毎に Builder 具象クラスを実装する必要がある、ということです。つまり、構築と最終オブジェクト作成の分離、という長所は短所にもなり得る、ということになります。
ですから、どのような場面で builder pattern を採用し、どのような時には使うべきではない、ということを判断するのが非常に重要になってきます。ビルダーパターンを採用すべきではない状況の1つは、作成後にも設定値の変更等を許可したいオブジェクト (mutable objects) を作成したい場合です。また、多数のコンストラクタを記述する必要がない場合やコンストラクタの内容が非常に単純な場合、また、コンストラクタのパラメータのデフォルト値として設定する妥当な値がなく、その都度明示的に指定する必要がある場合なども builder pattern の採用は避けるべきでしょう。
我々が「抽象 (abstraction)」と口にする際の最も一般的な意味合いは「物事と名前の関連付け」ということです。C 言語におけるマシンコード (machine code) はマシン命令 (machine instructions) を表すラベルによって抽象化されています。そしてこのマシンコードは、16進数で表現された命令セット (set of hexadecimal instructions) であり、マシンが実際に「解読」する2進数を抽象化したものです。Python においてはオブジェクト (objects) や関数 (functions) によって更に抽象化が積み重なっていることになります (もちろん C 言語にも関数やライブラリが存在します)。