はじめに
変更しやすいコードが書けないのにソフトウェア開発とか舐めているのですか
天使様1ごめんなさい〜、nikkieです。
2024年の干支は辰だと思っていましたが、天使様!という可能性に心が踊っています2
「かわいい」と技術書が夢の合体を果たした、ちょうぜつ本(『ちょうぜつソフトウェア設計入門』)!🤗
技術書界のきららという評もあります(アニメ企画来てくれ〜!)
昨年から読書会を共同主催しており、現在は第8章「デザインパターン」を読み進めています。
次回の範囲の予習から、Proxyを取り上げます。
目次
- はじめに
- 目次
- 前回のちょうぜつ本!
- Proxyパターン
- Proxyの例
- Pythonはデコレータという文法でProxyパターンができる
- 「マンガでわかる Proxy」より
- 終わりに
- P.S. 1/5(金) 第8章「デザインパターン」(8-6)のちょうぜつ本_読書py!
前回のちょうぜつ本!
前から順に読み進めて第8章に突入し、読書会では範囲を細かくしてじっくりと読み進めています。
- 読書ログ | #ちょうぜつ本 第8章 Template Methodパターン 〜穴埋め問題にする〜 - nikkie-ftnextの日記
- 読書&写経ログ | #ちょうぜつ本 第8章 Bridgeパターン 〜has-a関係でとらえて、複数系統の組合せを表現できる!〜 - nikkie-ftnextの日記
- 読書ログ | #ちょうぜつ本 (第8章)でも #fukabori (48)でも、ただしSingleton、テメーはダメだ - nikkie-ftnextの日記
- 読書ログ | #ちょうぜつ本 第8章 Facadeパターン 〜表玄関だけを通って!〜 - nikkie-ftnextの日記
Mediatorはむずかしかったですが、完全に理解しました。
Proxyパターン
呼び出し元と呼び出し先のオブジェクトの間に割って入ります。
呼び出し先オブジェクトをラップする(包む)わけです
ラッパーのパターンにはProxyだけではなく、他にDecoratorとAdapterもあります。
ちょうぜつ本ではこれら3つを目的で区別して解説していました。
比較したことで分かりやすいと思ったのが以下です。
元のコードを変更せずに、同じ機能呼び出しの振る舞いを拡張するためのパターン (Kindle版 p.376)
利用者から見たメソッドの使い方を変えずに、特定の振る舞いを拡張するラッパー (Kindle版 p.380)
Proxyが同じ型に見せかけた別の振る舞いを生み出す (Kindle版 p.384)
引用2つ目と3つ目は例と合わせて納得しました。
Proxyの例
メールを送信するジョブの例です。
class MailerInterface(Protocol): def send(self, mail: Mail) -> None: ... class JobWorker: def __init__(self, mailer: MailerInterface) -> None: self.mailer = mailer def process(self) -> None: report_mail = Mail() self.mailer.send(report_mail) class RealMailer: def send(self, mail: Mail) -> None: print("メール送信") worker = JobWorker(RealMailer()) worker.process()
ここでメール送信処理のログ出力を考えます。
Proxyパターンを使って、MailerInterfaceを実装し(=sendメソッドを持ち)、かつ、ログ出力3もするクラスを実現できます。
class LoggingInterface(Protocol): def info(self, message: str) -> None: ... class LoggingMailerProxy: def __init__( self, target: MailerInterface, logger: LoggingInterface ) -> None: self.target = target self.logger = logger def send(self, mail: Mail) -> None: self.logger.info(f"Before send {mail.address}") self.target.send(mail) self.logger.info(f"After send {mail.address}") class StdlibLogger: ... # この記事では省略(GitHub側をどうぞ) worker = JobWorker(LoggingMailerProxy(RealMailer(), StdlibLogger())) worker.process()
LoggingMailerProxy
はMailerInterface
を実装しているLoggingMailerProxy
のインスタンスはJobWorker
に渡せる
LoggingMailerProxy
のsend
メソッドはMailerInterface
のsend
メソッドの前後にログ出力処理を挟む- この
MailerInterface
はRealMailer
を想定 - つまり、
RealMailer
のsend
メソッドに(RealMailer
自体は変えずに)ログ出力が追加された
- この
ソースコード全体はこちらからどうぞ
Pythonはデコレータという文法でProxyパターンができる
できることを示すために雑に書いた実装です。
(本番コードに組み込むなら、logging
モジュールを使ったloggerの初期化を1回限りにするなど工夫したいです)
def enable_mail_logging(func): # logger初期化処理(省略) def wrapper(mailer, mail) -> None: logger.info(f"Before send {mail.address}") func(mailer, mail) logger.info(f"After send {mail.address}") return wrapper class RealMailer: @enable_mail_logging # これだけでログ出力が追加できた!🙌 def send(self, mail: Mail) -> None: print("メール送信")
デコレータは関数を返す関数です4。
func関数を受け取り、func関数の前後に処理を追加したwrapper関数を返します。
@
の記法により、デコレータが返す関数(wrapper
)はデコレータに渡された関数の名前(send
)に再代入されます。
Proxyパターンの目的、RealMailerに手を入れずにログ出力を追加するというのは、Python文法のデコレータでも実現できました!
ちょうぜつ本のコードの方が小さいものが寄せ集まっている綺麗さを感じるので、ここで示したデコレータ実装は伸びしろがあります(この場合は、デコレータとして振る舞うクラスにしたい気持ち)
コードの全体はこちらのスクリプトをどうぞ
「マンガでわかる Proxy」より
ちょうぜつ本の元になったアドベントカレンダー
Proxy は Adapter や Decorator とは目的が異なり、かならずしもラップ対象に委譲するわけではない、というのがポイントです。
Proxyの利用シーンとして、負荷対策や遅延評価が挙がっています。
Proxy の目的は「対外的な結果を変えずに、いかに本来の機能をサボるか」
ちょうぜつ本で完全に理解した!となりましたが、アドベントカレンダーを覗いたところ、負荷対策や遅延評価のようなサボる実装は独力ではまだできそうにないなと鼻柱を折られました。
実装例を探してみよう5
終わりに
ちょうぜつ本 8章よりProxyパターンでした。
Pythonの文法にあるデコレータを実装した経験から、私にはとっつきやすかったです。
おまけ:GPT-4に頼んで「まんがタイムきらら」風に表現したProxyデザインパターンのイラスト
P.S. 1/5(金) 第8章「デザインパターン」(8-6)のちょうぜつ本_読書py!
次回ちょうぜつ本_読書py(Python使い視点でちょうぜつ本を読む、みんなのアウトプット中心の読書会)は1/5(金)です!
ちょうぜつ本_読書py[10] に参加を申し込みました!年始に挑むデザインパターンはこいつら:Proxy!Decorator!!そしてAdaptor!!!Pythonのデコレータとは違うっぽい...? https://t.co/u8GBOeaALc #ちょうぜつ本
— nikkie / にっきー (@ftnext) 2023年12月17日
常連さんも、お久しぶりの方も、はじめましての方もデザインパターンに興味ある方、大歓迎です!
ぜひぜひお気軽にお越しください〜
- ちょうぜつ本読書ログシリーズではおなじみのこちらの書き出し。元は『お隣の天使様にいつの間にか駄目人間にされていた件』の「家事ができないのに一人暮らしとか舐めているのですか」です↩
-
「真昼だらけの一年」🤩
↩🎍2023年もあと1️⃣日🎍
— TVアニメ『お隣の天使様にいつの間にか駄目人間にされていた件』【公式】 (@tenshisama_PR) 2023年12月31日
今年も応援して下さった皆様、
本当にありがとうございました❣️
来年も真昼だらけの一年になりますので、
皆様、今後の展開をお楽しみに🙏✨#お隣の天使様 pic.twitter.com/l9nn7hmqnL - easyな方法のルートロガーは採用せずに、子のロガーを用意してそれに追加したハンドラがどちらもinfo以上をロギングするように実装しています ↩
- 用語集 https://docs.python.org/ja/3/glossary.html#term-decorator↩
- 「単純にメモ化してキャッシュ」はPython文法のデコレータでやってみたことあるかも↩