「重たい処理を毎回実行してアプリが遅い…」「本体の機能に直接触らせたくない…」そんな悩みはありませんか?
プロキシパターンは、実際のオブジェクトの“代理人”を用意して、アクセス方法を上手にコントロールするデザインパターンです。
Python初心者の方でも、日常の例に置き換えるとイメージしやすくなります。例えば、映画館の入場ゲート。映画館(本体)に入る前に、チケット係(プロキシ)が“本当に入れる人か”を確認してから通しますよね。プロキシパターンは、まさにこの「窓口役」をプログラムに取り入れる考え方です。
この章では、Pythonでの具体的な実装方法から、どんな場面で役立つのか、よくある注意点までわかりやすく丁寧に解説します。
プロキシパターン(Proxy Pattern)は、オブジェクトへのアクセスを“代理オブジェクト(プロキシ)”で挟むことで、実体(リアルサブジェクト)への処理を遅らせたり、制限したり、付加機能を与えたりするパターンです。これにより、不要なタイミングで重たい処理を実行しない遅延初期化や、キャッシュ、ログ記録、アクセス制御などをシンプルに実現できます。
「表示するまで画像ファイルを読み込みたくない」「有料ユーザーだけ使える機能にしたい」「外部APIへのアクセス回数を抑えたい」──そんなニーズにぴったりです。
まずは、プロキシパターンが活躍する場面を具体的にイメージしてみましょう。どんなときに導入すべきかがわかると、設計の判断がグッと楽になります。
このあとコードを読む前に、プロキシパターンの構成要素を整理しておくと理解がスムーズです。
ここでは、画像を表示するプログラムを例に、Pythonでプロキシパターンを実装してみます。
最初から画像を読み込むのではなく、実際に表示するときにだけ読み込むようにして、無駄なコストを避けます。「表示要求が来るまで読み込まない」というのがポイントです。
まずは、クライアントが呼び出す共通のインターフェースを用意します。リアルサブジェクトとプロキシの両方がこれを実装することで、呼び出し側は“どちらが本体か”を意識せずに扱えます。
from abc import ABC, abstractmethod
class Image(ABC):
@abstractmethod
def display(self):
"""画像を表示するための共通メソッド"""
pass
次に、本体である「実際の画像」を表現します。ここでは読み込みコストが高い処理をシミュレーションするため、生成時に読み込み処理を行っています。
#上記コードの続き
class RealImage(Image):
def __init__(self, filename: str):
self.filename = filename
self.load_image_from_disk()
def load_image_from_disk(self):
print(f"Loading {self.filename}")
def display(self):
print(f"Displaying {self.filename}")
プロキシは、本体をすぐには作らず、必要になったときだけ作る“遅延初期化”を担当します。はじめは何もしない軽量なオブジェクトとして存在し、displayが呼ばれた瞬間にリアルサブジェクトを生成します。
#上記コードの続き
class ProxyImage(Image):
def __init__(self, filename: str):
self.filename = filename
self.real_image = None # まだ本体を作らない(遅延)
def display(self):
if self.real_image is None:
self.real_image = RealImage(self.filename)
self.real_image.display()
最後に、実際に使ってみます。最初の表示では読み込みが走りますが、2回目以降は同じ画像なら読み込みが省略されることを確認しましょう。
#上記コードの続き
image1 = ProxyImage("image1.jpg")
image2 = ProxyImage("image2.jpg")
# 最初の表示では画像をディスクから読み込む
image1.display()
print("")
# 2回目の表示では再度読み込まない
image1.display()
print("")
# 2つ目の画像を初めて表示する
image2.display()
実行すると、次のような出力が得られます。表示要求のタイミングまで読み込みが遅延していることがわかります。
Loading image1.jpg
Displaying image1.jpg
Displaying image1.jpg
Loading image2.jpg
Displaying image2.jpg
このコードでは、クライアントが触れるのはImageという共通インターフェースだけです。RealImageはコンストラクタで読み込み処理を行うため、本体を作った瞬間にコストが発生します。一方で、ProxyImageは作成直後は本体を持たず、とても軽量です。displayが呼ばれたときに、初めてRealImageを生成して読み込みを行います。
つまり、「本当に必要になるまで重たい処理を先延ばしにする」仕組みになっているのです。2回目以降のdisplay呼び出しでは、すでに生成済みのRealImageをそのまま使うため、読み込みの手間が省け、パフォーマンスが向上します。
また、呼び出し側はimage1やimage2が本体なのかプロキシなのかを意識する必要がありません。どちらもImageインターフェースを満たしているので、同じ使い方ができるからです。これにより、将来の拡張や差し替えが容易になり、メンテナンス性の高いコードになります。
プロキシパターンは、Pythonでのデザインパターン学習の中でも早い段階で“効果を実感しやすい”パターンです。
今回の画像表示の例のように、遅延初期化によって無駄なコストを削減でき、アプリの体感速度やリソース効率が向上します。さらに、アクセス制御、キャッシュ、ログ、リトライなど、現場でよくあるニーズもきれいに表現できます。
「本体に触る前に、何をどこでコントロールしたいか?」と考えるだけで、プロキシを使うべきかどうかの判断がつきやすくなります。次にコードを書くとき、重たい処理や制御ロジックをどこに置くか迷ったら、今回のプロキシパターンを思い出してみてください。
あなたのPythonコードが、より速く、より安全で、より拡張しやすいものに進化するはずです。