検索ガイド -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 Chain of Responsibility Pattern - Part. 5 「より pythonic な Chain of Responsibility パターンの実装とプロジェクトへの適用」の巻 投稿一覧へ戻る

Published 2022年6月16日21:37 by T.Tsuyoshi

SUPPORT UKRAINE

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

A More Pythonic Implementation
(より pythonic な Chain of Responsibility パターンの実装とプロジェクトへの適用)

このパターンの従来の実装方法では、ハンドラークラスの雛形となるインターフェースクラス (抽象クラス) を定義します。この例では、そのクラスを継承する具象ハンドラークラスでは next_to_execute 変数を定義し、execute() メソッドを実装することになるでしょう。
しかし既に何回も見てきたように、ダックタイピング言語である Python ではこの作業を省いてしまうことができます。「機能チェーン」内で利用したいそれぞれのハンドラーが「同じパーツ」で構成されてさえいれば、いちいち共通のベースクラスを継承する必要はありません。これによって、定型コード (boilerplate code) の記述に費やす時間を大幅に節約できるだけではなく、アプリケーションやシステムの要求に応じて簡単に機能を進化させることが可能になります。
より制約の強い言語では、その場その場に応じてデザインを進化させる、といった贅沢は許されません。そのような方針転換は、多くの場合大規模な設計見直しとコードの書き換えが必要となります。システム全体にわたるそのような再設計やアプリケーションの書き換えといった事態が生じないように、エンジニアや開発者は考えつく限りの可能性に対処できるようなシステムを構築しようとします。
しかしこのアプローチ方法には2つの重大な欠点があります。1つは、人間が予測できる将来は不完全なものでしかない、ということです。ですから、もし現時点における完璧なシステムを設計できたとしても (このこと自体がほぼ不可能ですが...)、すべてのクライアントのシステムに対する要求を考慮することなど不可能ですし、将来的にマーケットが何を必要とするか、などを完全に予測することも不可能です (これが予測できるのならエンジニアなどすぐ辞めて投資家を志すべきです)。
そして2つ目の「当てはまらないことなど無い」ほど確実なことは、You Ain't Gonna Need It (YAGNI: それって必要ないんじゃない) 、つまり、設計段階で「あれもこれも」とカバーした「将来的に必要となるであろう機能」のほとんどは実際に必要となることはない、ということです。そしてそのために費やした時間も努力も結果的に無駄になる、ということです。
Python を利用することで、あなたは、目の前にある小さな問題を解決するための単純な解決方法、を記述することから始めることができます。そして必要とされる機能や使い途が変化したり増加したら、より複雑な解決方法へと進化させていくことができます。囲碁と同じように始めるのも簡単です。そして多くの可能性を習得するための探求心に満ち溢れた一生を送ることができます。
ここで、余分なものを全て剝ぎ取った最もシンプルな Chain of Responsibility パターンの骨格を載せておきたいと思います:
general_structure_of_chain_of_responsibility_pattern.py
class EndHandler:
def __init__(self):
pass

def handel_request(self, request):
pass


class Handler_1:
def __init__(self):
self.next_handler = EndHandler()

def handle_request(self, request):
self.next_handler.handel_request(request)


def main(request):
concrete_handler = Handler_1()
concrete_handler.handle_request(request)

if __name__ == '__main__':
# コマンドライン等から何かしらのリクエストを受け付けます
main(request)
いよいよこの章の最初で取り上げていた我々の web アプリケーションにこの Chain of Responsibility パターンの骨格を埋め込み進化させる時がやってきました。このパターンの導入によって single responsibility principle (単一責任の原則) をも順守した実装となります。また、あなた自身が必要とする拡張機能の実装にもぜひ挑戦してください:
middleware_with_chain_of_responsibility_pattern.py
import base64


class User:
def __init__(self, username, password, name, email):
self.username = username
self.password = password
self.name = name
self.email = email

@classmethod
def get_verified_user(cls, username, password):
return User(
username,
password,
username,
f'{username}@example.com'
)


class EndHandler:
def __init__(self):
pass

def handle_request(self, request, response=None):
print('EndHandler')
return response.encode('utf-8')


class AuthorizationHandler:
def __init__(self):
self.next_handler = EndHandler()

def handle_request(self, request, response=None):
print('AuthorizationHandler')
authorization_header = request['HTTP_AUTHORIZATION']
header_array = authorization_header.split()
encoded_string = header_array[1]
decoded_string = base64.b64decode(encoded_string).decode('utf-8')
username, password = decoded_string.split(':')
request['username'] = username
request['password'] = password

return self.next_handler.handle_request(request, response)


class UserHandler:
def __init__(self):
self.next_handler = EndHandler()

def handle_request(self, request, response=None):
print('UserHandler')
user = User.get_verified_user(request['username'], request['password'])
request['user'] = user

return self.next_handler.handle_request(request, response)


class PathHandler:
def __init__(self):
self.next_handler = EndHandler()

def handle_request(self, request, response=None):
print('PathHandler')
path = request['PATH_INFO'].split('/')
if 'goodbye' in path:
response = f'Goodbye {request["user"].name}!'
else:
response = f'Hello {request["user"].name}!'

return self.next_handler.handle_request(request, response)


def application(env, start_response):
head = AuthorizationHandler()

current = head
current.next_handler = UserHandler()

current = current.next_handler
current.next_handler = PathHandler()

start_response('200 OK', [('Content-Type', 'text/html')])
return [head.handle_request(env)]
The Chain of Responsibility パターンを利用した実装方法としては、それぞれの機能を受け持つハンドラーをリクエストの受け渡しを介して次々と呼び出していく Dispatcher クラスを定義する、というものもあります。main_application() では、Dispatcher クラスのインスタンス化時に「機能チェーン」を構成するハンドラークラス (もしくは、ハンドラー関数) からなるリストを渡すことになります:
class Dispatcher:
def __init__(self, handlers=[]):
self.handlers = handlers

def handle_request(self, request):
for handler in self.handlers:
request = handler(request)

return request
この Dispatcher クラスでは、Python において扱う全てのものはオブジェクトである、という事実を利用しています。関数も例外ではなく first-class citizens (第一級関数) ですから、関数に対する引数にもなれば返り値にもなり得ます。
実装としては main_function() において Dispatcher オブジェクトを作成し、その時点で「機能チェーン」を形成する関数、もしくは、クラスのリストを渡します。Dispatcher オブジェクトではそのリストをループしながら、機能オブジェクトを呼び出すたびにリクエストを渡し、また、返されたリクエストを次のチェーンへとつないでいきます:
middleware_with_chain_of_responsibility_pattern_using_dispatcher.py
import base64


class User:
def __init__(self, username, password, name, email):
self.username = username
self.password = password
self.name = name
self.email = email

@classmethod
def get_verified_user(cls, username, password):
return User(
username,
password,
username,
f'{username}@example.com'
)


class EndHandler:
def __init__(self):
pass

def handle_request(self, request, response=None):
print('EndHandler')
return response.encode('utf-8')


class AuthorizationHandler:
def __init__(self):
self.next_handler = EndHandler()

def handle_request(self, request, response=None):
print('AuthorizationHandler')
authorization_header = request['HTTP_AUTHORIZATION']
header_array = authorization_header.split()
encoded_string = header_array[1]
decoded_string = base64.b64decode(encoded_string).decode('utf-8')
username, password = decoded_string.split(':')
request['username'] = username
request['password'] = password

return self.next_handler.handle_request(request, response)


class UserHandler:
def __init__(self):
self.next_handler = EndHandler()

def handle_request(self, request, response=None):
print('UserHandler')
user = User.get_verified_user(request['username'], request['password'])
request['user'] = user

return self.next_handler.handle_request(request, response)


class PathHandler:
def __init__(self):
self.next_handler = EndHandler()

def handle_request(self, request, response=None):
print('PathHandler')
path = request['PATH_INFO'].split('/')
if 'goodbye' in path:
response = f'Goodbye {request["user"].name}!'
else:
response = f'Hello {request["user"].name}!'

return self.next_handler.handle_request(request, response)


class Dispatcher:
def __init__(self, handlers=[]):
self.handlers = handlers

def handle_request(self, request):
head = self.handlers[0]()
current = head

for handler in self.handlers[1:]:
current.next_handler = handler()
current = current.next_handler

return head.handle_request(request)


def application(env, start_response):
dispatcher = Dispatcher([
AuthorizationHandler,
UserHandler,
PathHandler,
])

start_response('200 OK', [('Content-Type', 'text/html')])
return [dispatcher.handle_request(env)]
我々の web アプリケーションへの Dispatcher クラス適用の一例です。サーバーアプリケーションのエントリポイントである application() と Dispatcher クラスの追加以外前回のコードからの変更はありません。
この例ではメインアプリケーションから Dispatcher オブジェクトに対して「機能チェーン」を構成する「クラス」のリストを渡しています。Dispatcher オブジェクトでは、実際のリクエスト処理時にそれぞれのクラスのオブジェクトを作成、機能チェーンの構築、実行を行っています。ただし、メインアプリケーションから「機能チェーン」を構成する「クラスのオブジェクト」のリストを渡し、Dispatcher クラスではインスタンス化することなく直にそのオブジェクトを利用することも可能でしょう。
いずれにしても、Dispatcher クラスを導入することで、メインアプリケーションのコードが非常にクリーンになることが分かると思います。

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

0 comments

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

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