ログインボックスを表示します

検索ガイド -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 Facade Pattern - Part. 2 「システムの進化のために」の巻 投稿一覧へ戻る

Published 2022年6月9日18:15 by T.Tsuyoshi

SUPPORT UKRAINE

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

Practical Python Design Patterns - The Facade Pattern 編

Systems Evolution
(システムの進化のために)

日常的にシステムは進化し続けます。そして進化していくほどに複雑になってくるのも事実です。しかし、その複雑さをクライアントに押し付けるのは筋違いです。クライアントからはその複雑さを隠さなければなりません。
前のセクションで提示した POS システムの例を見てどう感じたでしょうか?お互いに密接に関連し合うおびただしい数のクラスが存在します。もしこのシステムに新しいテクノロジーを導入したり新しい機能を追加するとなった場合、システムの詳細を精査する必要に迫られるでしょう。そして、もし、システム内の少し入り組んだ奥底に潜むコードのアップデートを忘れてしまえば、途端に全てのシステムはクラッシュしてしまうかもしれません。このシステムが有するいかなる能力、機能を失うことなく、システム全体をより単純化する必要があります。
次のように業務を処理できれば理想的です:
def nice_sales_processor(customer_code, item_dict_list, payment_type):
invoice = SomeInvoiceClass(customer_code)

for item_dict in item_dict_list:
invoice.add_item(item_dict['barcode'], item_dict['amount_purchased'])

invoice.finalize()
invoice.generate_receipt(payment_type)
最初の例のように、関連し合う複数のクラス (サブシステム) を処理の流れに従って羅列するよりも、非常に読みやすくクリーンになっていますよね。オブジェクト指向プログラミングにおける基本原則は、「醜く煩雑なシステムに遭遇したらオブジェクトで隠せ」です。
「醜く煩雑なシステム」は、プログラマーとしてキャリアを重ねていれば定期的に出くわす問題ですから、この問題に対する「最良の解決策」がいくつか提案されていることも想像に難くありません。その内の1つが、facade pattern (ファサードパターン) と呼ばれるデザインパターンの採用です。このパターンは、サブシステム、もしくは、それらの集まりに対してアクセスする際の「より複雑さの少ないインターフェース」を実装したい場合によく利用されています。
Facade pattern による抽象化がどのように利用されるのか、を感覚的につかむために以下のコードを見てください:
simple_facade.py
class Invoice:
def __init__(self, customer):
pass


class Item:
# 初期化時に商品情報を取得したり、新規商品の作成を行えるように Item クラスを変更します
def __init__(self, item_barcode):
pass


class Customer:
# 初期化時に顧客情報を取得したり、新規顧客の作成を行えるように Customer クラスを変更します
def __init__(self, customer_id):
pass


class Facade:
@staticmethod
def make_invoice(customer):
return Invoice(customer)

@staticmethod
def make_customer(customer_id):
return Customer(customer_id)

@staticmethod
def make_item(item_barcode):
return Item(item_barcode)

...

What Sets the Facade Pattern Apart
(ファサードパターンの特徴)

ファサードパターンの特徴の1つは、単一のオブジェクトをカプセル化するだけではなく、複雑なサブシステムをラップして、クライアントには不要な機能や見せなくてもよい複雑さを取り除いたシンプルなインターフェースを提供し使い勝手を向上させる、というものです。システムの開発者であれば、外から見えるシステムの詳細や複雑さの範囲を可能な限り制限したい、と思うのは当然でしょう。システムへのアクセス許可範囲を拡大するほど、そのシステムを構成する各機能の結び付きはより強固にならざるを得ませんから (tightly-coupled system: 密結合システム)。最終的に目標とすべきは、システムの深淵部分はブラックボックスでありながら、実は全機能をちゃんと享受できているシステムです。明確に定められた入力データ形式があり、それに対する必要十分な定型化した出力があるシステムです。
Facade は、どの内部クラスを利用するかを決定します。また、必要に応じた外部や内部の更新されたシステムに対する「割り当て」の変更もファサードの役目になります。
ファサードが提供する「理解しやすく」「簡易な」メソッドによってシステムの使用が容易になるため、facade pattern はソフトウェアライブラリの構築・提供に良く採用されています。また、利用する側からだけではなくソフトウェアライブラリ側からしても、ファサードを介することで外部と内部の結びつきを弱めることが可能になります。これによって他のシステム同様、設計・開発時の柔軟性を高めることにも繋がるわけです。
また、あまり考慮されていないデザインのまま提供されている API を利用せざる負えない場合もあるかもしれません。こういった状況でも、ファサードパターンを利用してそれらの API を「熟慮して設計した」1つの API でラップすることで、開発を円滑に進められるようになる可能性があります。
身に着けるべき最も有用な習慣の1つは、ある問題に対する自分なりの解決策を「創り出す」ことです。もし、理想には程遠いシステムを利用しなければならないのであれば、自分が利用しやすくなるようなファサードを構築すればいいのです。もし、使用している IDE に対して利用したいツールが提供されていないのであれば、その機能を実現するための extension を自分自身でコーディングすればいいのです。他の人がそういったツールを提供してくれるのを待っていてはいけません。自分のニーズに合う環境を整えるためのアクションを起こすことで、日々の暮らしの充実度がより増すはずです。
さて、ここまで取り上げてきた POS システム例をファサードとして実装してみましょう。ここですべてのシステムを作り上げる、という大作業をするつもりはありませんから、複数の関数はプレースホルダーとして記述してあるだけです:
class Sale:
def __init__(self):
pass

@staticmethod
def make_invoice(customer_id):
return Invoice(Customer.fetch(customer_id))

@staticmethod
def make_customer():
return Customer()

@staticmethod
def make_item(item_barcode):
return Item(item_barcode)

@staticmethod
def make_invoice_line(item):
return InvoiceLine(item)

@staticmethod
def make_receipt(invoice, payment_type):
return Receipt(invoice, payment_type)

@staticmethod
def make_loyalty_account(customer):
return LoyaltyAccount(customer)

@staticmethod
def fetch_invoice(customer_id):
return Invoice(customer_id)

@staticmethod
def fetch_customer(customer_id):
return Customer(customer_id)

@staticmethod
def fetch_item(item_barcode):
return Item(item_barcode)

@staticmethod
def fetch_invoice_line(item_id):
return InvoiceLine(item_id)

@staticmethod
def fetch_receipts(invoice_id):
return Receipt(invoice_id)

@staticmethod
def loyalty_account(customer_id):
return LoyaltyAccount(customer_id)

@staticmethod
def add_item(invoice, item_barcode, amount_purchased):
item = Item.fetch(item_barcode)
item.amount_in_stock -= amount_purchased
item.save()
invoice_line = InvoiceLine.make(item)
invoice.add_line(invoice_line)

@staticmethod
def finalize(invoice):
invoice.calculate()
invoice.save()

loyalty_account = LoyaltyAccount.fetch(invoice.customer)
loyalty_account.calculate(invoice)
loyalty_account.save()

@staticmethod
def generate_receipt(invoice, payment_type):
receipt = Receipt(invoice, payment_type)
receipt.save()
各サブシステムに対するファサードとして機能するこの新しい Sale クラスを利用することで、この章の先頭で挙げた「理想的な」業務処理関数を次のように実装できるようになります:
def nice_sales_processor(customer_id, item_dict_list, payment_type):
invoice = Sale.make_invoice(customer_id)

for item_dict in item_dict_list:
Sale.add_item(invoice, item_dict['barcode'], item_dict['amount_purchased'])

Sale.finalize(invoice)
Sale.generate_receipt(invoice, payment_type)
ほとんど変更点がないことがお分かりでしょうか?
この Sale クラスは、より複雑なビジネスシステムを利用する際の唯一のエントリポイントとして機能します。そしてこの「ファサード」が提供するメソッドは、我々がこの複雑なシステムを利用する際に呼び出す必要がある機能だけを網羅しており、「知らなくてもよい」システム内部の複雑さに圧倒されることもなくなります。このシステムの利用者はたった1つの Sale クラスとのやり取りに集中すればよく、どのクラスをどの順番で使用しなければいけないのか、といったシステムを構成する全てのクラスダイアグラムを考慮する必要は一切ありません。
またシステム側からすれば、在庫管理システムや顧客のポイント等を処理するロイヤリティシステムといったサブシステムを内部で開発したものからサードパーティーが提供するものに変更することになったとしても、このファサードクラスの利用者に何らかの変更を求める必要もなく実行することが可能になります。すなわちこのシステムを構成するサブシステム同士はより loosely coupled (疎結合) となり、結果として拡張が容易になった、ということです。
この POS システムの利用者にとって、在庫システムや会計システムが外部委託されていようが内部的に処理されていようがそれは考慮すべき問題ではありません。簡素化されたインターフェースによってそういったシステム内部の複雑な構成は見事に隠蔽される一方、サブシステム自体が持つパフォーマンスを犠牲にする心配もありません。

Parting Shots
(ダメ押し確認)

ファサードパターンについて語ることはそれほど多くはありません。もし、サブシステムやそれらの集まりに対して「自分の思い通りに利用できない」といったような不満を覚えたり、多くのクラスにアクセスしなければならない状況に追い込まれたりした場合、この章を読み進んできたあなたであれば何が間違っているのかを判断できるようになっているはずです。この状況を改善するために、全体のクリーンアップに取り組むか、少なくとも、こういった煩雑なシステムを1つの整理されたインターフェースでラップしてしまおう、という決断を下すでしょう。
ラッパークラスを設計し、こういった「醜い」サブシステム群をブラックボックス化します。サブシステム群は複雑な処理をこなす裏方に徹してもらうわけです。こうすれば、このサブシステム群の機能を利用したいあなた自身、および、他のクライアントは、ごちゃごちゃしたシステムの内部事情に関知する必要はなくこのファサードとのやり取りに集中すればよいのです。あなたの努力によって世界はまたちょっと良くなりました; お疲れさまでした。

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

0 comments

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

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