検索ガイド -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 Adapter Pattern - Part. 5 「実際のプログラムへのアダプターパターン適用」の巻 投稿一覧へ戻る

Published 2022年6月2日18:45 by T.Tsuyoshi

SUPPORT UKRAINE

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

Implementing the Adapter Pattern in the Real World
(実際のプログラムへのアダプターパターン適用)

ここまで読み進んできて、アダプターパターンの明確で pythonic な実装方法が理解できつつあると思いますから、この章の最初で取り上げたメール送信プログラムに適用してみましょう。ここではアダプターパターンを採用したプログラムが実際に動作する様子をお見せするために、作成したメール送信機能クラスのインターフェースを利用してターミナル(コマンドプロンプト)にメッセージを出力するためのプログラムを利用するためのアダプターを実装してみたいと思います:
import csv
import smtplib
from email.mime.text import MIMEText

from user_fetcher import UserFetcher

class Mailer:
def send(self, sender, recipients: list[str], subject, message):
msg = MIMEText(message, _charset='utf-8')
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = ','.join(recipients)

with smtplib.SMTP(host='server_name', port=123) as mail_sender:
mail_sender.login('user_name', 'password')
errors = mail_sender.send_message(msg)

if isinstance(errors, dict) and len(errors) > 0:
print(f'Error: {errors}')


class Logger:
def output(self, message):
print(f'[Logger] {message}')


class LoggerAdapter:
def __init__(self):
self.what_i_have = Logger()

def send(self, sender, recipients: list[str], subject, message):
log_message = f'From: {sender}\nTo: {recipients}\nSubject: {subject}\nMessage: {message}'
self.what_i_have.output(log_message)

def __getattr__(self, attr):
return getattr(self.what_i_have, attr)


if __name__ == '__main__':
user_fetcher = UserFetcher('users.csv')
mailer = Mailer()
logger = LoggerAdapter()

for x in user_fetcher.fetch_user():
mailer.send(
'email@example.com',
[x['email']],
'ご挨拶とお知らせ',
f'{x["surname"]} {x["name"]} 様\n良い一日をお過ごしください'
)
logger.send(
'python_blog@getwebtips.net',
[x['email']],
'ご挨拶とお知らせ',
f'{x["surname"]} {x["name"]} 様\n良い一日をお過ごしください'
)
このプログラムでは、Logger クラスが「既に存在するプログラム or 外部のサービス」に相当する Adaptee ですから、このクラスのインターフェースを変更することは考えません。Mailer クラスは私たち自身が「使いたい」インターフェースを通して利用できるように実装したものです。前章でもお話ししたように Python はダックタイピング言語ですから、「望む」インターフェースを定義すべき抽象クラスは記述する必要がありませんが、もしそのクラス (Target) を明記するとしたら、そのクラスの全てのサブクラスは send(self, sender, recipients: list[str], subject, message) メソッドを実装することが義務付けられます。
Logger クラスのインターフェースは我々が利用したいものとは異なります。そこで、我々が「望む」インターフェースでアクセスできるようにするために Adapter クラスを定義します (LoggerAdapter)。このアダプタークラスでは Adaptee クラスのオブジェクトをインスタンス変数として所持し、我々のプログラムの共通インターフェースである send() メソッドを実装しています。そしてこのインターフェースメソッドを通して Logger クラスの従来のインターフェースを呼び出してサービスを利用しています。
利用例を見ていただければ分かるように、Mailer サービスも Logger サービスも全く同じインターフェースで利用可能になっています。
もし、外部サービスのインターフェース (この例でいえば Logger.output() ) のパラメータが私たちの「望む」インターフェース (この例でいえば send() ) のパラメータと全く同じであれば、よりジェネリックなアダプターを記述することが可能です。この場合、そのジェネリックアダプターには、利用したいサービスのオブジェクトとそのインターフェースを渡すだけで、サービスごとに特化したアダプターを記述する必要はありません:
class Logger:
"""
この Adaptee が取るパラメータが send() が取るものと同一であれば...
"""

def output(self, sender, recipients: list[str], subject, message):
print(f'[Logger] {message}')


class GeneralAdapter:
"""
これだけのアダプターを実装するだけで Adaptee が提供するサービスを利用できます
"""

def __init__(self, adaptee, adaptee_function):
"""
params adaptee: 利用したいサービス (Adaptee) のオブジェクト
params adaptee_function: Adaptee が提供しているインターフェースメソッド
"""

self.adaptee = adaptee
self.send = adaptee_function

def __getattr__(self, attr):
return getattr(self.adaptee, attr)

if __name__ == '__main__':
user_fetcher = UserFetcher('users.csv')
mailer = Mailer()
adaptee = Logger()
adapter = GeneralAdapter(adaptee, adaptee.output)

for x in user_fetcher.fetch_user():
mailer.send(
'python_blog@getwebtips.net',
[x['email']],
'ご挨拶とお知らせ',
f'{x["surname"]} {x["name"]} 様\n良い一日をお過ごしください'
)
adapter.send(
'python_blog@getwebtips.net',
[x['email']],
'ご挨拶とお知らせ',
f'{x["surname"]} {x["name"]} 様\n良い一日をお過ごしください'
)
アダプターパターンについての理解はかなり深まったのではないでしょうか?これは簡単な応用例でしたが、是非自分で Twitter に Tweet を送信 (投稿) するための Adapter を実装してみてください、そして他の sns にも。自分でコードを実装することで、直面する問題に対する「直感」を磨いていくことができます。そして、動作するものができたからといって満足するのではなく、さらに改善するために、手を加え、失敗し、悩み考え、一歩一歩ステップアップしていきましょう。
次回はアダプターパターンの最終回として、共通インターフェースを利用して Twitter に Tweet を投稿するプログラムの一例を示したいと思います。が、その前に是非自分で実装してみてください。

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

0 comments

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

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