<一覧に戻る

プロキシパターン

「重たい処理を毎回実行してアプリが遅い…」「本体の機能に直接触らせたくない…」そんな悩みはありませんか?

プロキシパターンは、実際のオブジェクトの“代理人”を用意して、アクセス方法を上手にコントロールするデザインパターンです。

Python初心者の方でも、日常の例に置き換えるとイメージしやすくなります。例えば、映画館の入場ゲート。映画館(本体)に入る前に、チケット係(プロキシ)が“本当に入れる人か”を確認してから通しますよね。プロキシパターンは、まさにこの「窓口役」をプログラムに取り入れる考え方です。

この章では、Pythonでの具体的な実装方法から、どんな場面で役立つのか、よくある注意点までわかりやすく丁寧に解説します。

プロキシパターンとは?

プロキシパターン(Proxy Pattern)は、オブジェクトへのアクセスを“代理オブジェクト(プロキシ)”で挟むことで、実体(リアルサブジェクト)への処理を遅らせたり、制限したり、付加機能を与えたりするパターンです。これにより、不要なタイミングで重たい処理を実行しない遅延初期化や、キャッシュ、ログ記録、アクセス制御などをシンプルに実現できます。

「表示するまで画像ファイルを読み込みたくない」「有料ユーザーだけ使える機能にしたい」「外部APIへのアクセス回数を抑えたい」──そんなニーズにぴったりです。

いつ役立つ?(使いどころのイメージをつかもう)

まずは、プロキシパターンが活躍する場面を具体的にイメージしてみましょう。どんなときに導入すべきかがわかると、設計の判断がグッと楽になります。

  • 遅延初期化をしたいとき : 大きな画像・動画・データベース接続など、コストの高い処理を“必要になるまで作らない”でアプリを軽くします。
  • アクセス制御(認可/権限チェック)を挟みたいとき : ユーザーの権限に応じて機能の利用可否を決め、直接本体に触らせない安全な設計にできます。
  • キャッシュやログ、メトリクスを追加したいとき : 本体のコードを汚さずに、プロキシ側でキャッシュやログ出力、計測を付けられます。
  • リモートアクセスの窓口にしたいとき : ネットワーク越しのオブジェクトへの呼び出し(リモートプロキシ)の受け口として機能します。

プロキシパターンの構成要素

このあとコードを読む前に、プロキシパターンの構成要素を整理しておくと理解がスムーズです。

  • サブジェクト(Subject) : クライアント(利用者)が知っている共通インターフェース。リアルサブジェクトとプロキシは同じインターフェースを実装します。
  • リアルサブジェクト(RealSubject) : 実際の仕事をする本体。重たい処理や本来のビジネスロジックを持ちます。
  • プロキシ(Proxy) : 本体へのアクセスを代理・制御する窓口。遅延生成、権限チェック、キャッシュなどを引き受けます。

サンプルコード

ここでは、画像を表示するプログラムを例に、Pythonでプロキシパターンを実装してみます。

最初から画像を読み込むのではなく、実際に表示するときにだけ読み込むようにして、無駄なコストを避けます。「表示要求が来るまで読み込まない」というのがポイントです。

ステップ1: サブジェクトインターフェースの定義

まずは、クライアントが呼び出す共通のインターフェースを用意します。リアルサブジェクトとプロキシの両方がこれを実装することで、呼び出し側は“どちらが本体か”を意識せずに扱えます。

from abc import ABC, abstractmethod

class Image(ABC):
    @abstractmethod
    def display(self):
        """画像を表示するための共通メソッド"""
        pass

ステップ2: リアルサブジェクトの実装

次に、本体である「実際の画像」を表現します。ここでは読み込みコストが高い処理をシミュレーションするため、生成時に読み込み処理を行っています。

#上記コードの続き
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}")

ステップ3: プロキシの実装

プロキシは、本体をすぐには作らず、必要になったときだけ作る“遅延初期化”を担当します。はじめは何もしない軽量なオブジェクトとして存在し、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()

ステップ4: 使用例(挙動を確認してみよう)

最後に、実際に使ってみます。最初の表示では読み込みが走りますが、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コードが、より速く、より安全で、より拡張しやすいものに進化するはずです。

出力結果: