検索ガイド -Search Guide-

単語と単語を空白で区切ることで AND 検索になります。
例: python デコレータ ('python' と 'デコレータ' 両方を含む記事を検索します)
単語の前に '-' を付けることで NOT 検索になります。
例: python -デコレータ ('python' は含むが 'デコレータ' は含まない記事を検索します)
" (ダブルクオート) で語句を囲むことで 完全一致検索になります。
例: "python data" 実装 ('python data' と '実装' 両方を含む記事を検索します。'python data 実装' の検索とは異なります。)
当サイトのドメイン名は " getwebtips.net " です。
トップレベルドメインは .net であり、他の .com / .shop といったトップレベルドメインのサイトとは一切関係ありません。
practical_python_design_patterns

Practical Python Design Patterns - Python で学ぶデザインパターン: The Builder Pattern - Part. 2 「アンチパターン」の巻 投稿一覧へ戻る

Published 2022年5月29日1:25 by T.Tsuyoshi

SUPPORT UKRAINE

- Your indifference to the act of cruelty can thrive rogue nations like Russia -

Practical Python Design Patterns - The Builder Pattern 編

Anti-Patterns
(アンチパターン)

アンチパターンは、その名が示している通り、ソフトウェアパターン (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 におけるビルダーパターンのジェネリックな実装方法は次のようになります:
form_builder.py
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:
"""
Builder によって生成される要素の蓄積、ならびに、最終結果の提供
"""

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):
"""
最終オブジェクト構築のためのインターフェース:

最終生成オブジェクトを構成する各要素を生産する Builder の保持
その Builder を利用した各要素実体の作成 (抽象メソッド)
要素の集合体としての最終オブジェクトの提供
"""

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):
"""
具象 Builder クラス
"""

def __init__(self):
self.constructed_object = HtmlForm()

def add_text_field(self, field_dict):
self.constructed_object.field_list.append(
f"""{field_dict['label']}:<br>
<input type='text' name='{field_dict['field_name']}'><br>"""

)

def add_checkbox(self, checkbox_dict):
self.constructed_object.field_list.append(
f"""<input type='checkbox' id='{checkbox_dict['field_id']}'
value='{checkbox_dict['value']}'>
<label for='{checkbox_dict['field_id']}'>{checkbox_dict['label']}</label><br>"""

)

def add_button(self, button_dict):
self.constructed_object.field_list.append(
f"<button type='button'>{button_dict['text']}</button><br>"
)


class FormDirector(Director):
"""
具象 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() # Director のインスタンス化
html_form_builder = HtmlFormBuilder() # Builder のインスタンス化
director.set_builder(html_form_builder) # Director への 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',
}
]

# 各要素を作成、Builder オブジェクトのリスト属性に蓄積
director.construct(field_list)

with open('form_file.html', mode='w', encoding='utf-8') as f:
f.write(
# Builder が作成・蓄積した結果を取得
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 の採用は避けるべきでしょう。
A Note on Abstraction
(「抽象」について一言)
我々が「抽象 (abstraction)」と口にする際の最も一般的な意味合いは「物事と名前の関連付け」ということです。C 言語におけるマシンコード (machine code) はマシン命令 (machine instructions) を表すラベルによって抽象化されています。そしてこのマシンコードは、16進数で表現された命令セット (set of hexadecimal instructions) であり、マシンが実際に「解読」する2進数を抽象化したものです。Python においてはオブジェクト (objects) や関数 (functions) によって更に抽象化が積み重なっていることになります (もちろん C 言語にも関数やライブラリが存在します)。

この投稿をメールでシェアする

0 comments

コメントはまだありません。

コメントを追加する(不適切と思われるコメントは削除する場合があります)