PHP7でデザインパターン入門21/23 Proxyパターン

Proxy パターンとは

Proxy(プロキシ、代理人)とは、大まかに言えば、別の物のインタフェースとして機能するクラスである。その「別の物」とは何でもよく、ネットワーク接続だったり、メモリ上の大きなオブジェクトだったり、複製がコスト高あるいは不可能な何らかのリソースなどである。

Proxy パターン - Wikipedia

proxy(代理人)というのは、仕事を行うべき本人の代わりとなる人。本人でなくてもできるような仕事をまかせるために代理人を立てる。 オブジェクト指向では、「本人」も「代理人」もオブジェクトとなる。忙しくてその仕事ができない本人オブジェクトの代わりに、代理人オブジェクトが(ある程度)仕事をこなしてやることになる。

example

ここで作るサンプルプログラムは、「名前つきのプリンタ」。このプリンタは画面に文字を表示する。

まずPrinterProxyクラスのインスタンス(代理人)を生成する。そのインスタンスには「Alice」という名前をつけ、その名前を表示する。その後「Bob」という名前に変更し、その名前を表示する。名前の設定のや取得では、まだ本物のPrinterクラスのインスタンス(本人)は生成していない。名前の設定と取得という部分は、PrinterProxyクラスが代理で行う。最後にPrintメソッドを呼び出して、実際にプリントを行う段階になってはじめて、PrinterProxyクラスはPrinterクラスのインスタンスを生成する

ここでは、Printerクラスのインスタンス生成にとても時間がかかるという前提で、プログラムを作成している。コンストラクタからheavyJobメソッドを呼び出し、わざと「重い仕事」をさせている。

Printableクラス

Printer、PrinterProxyクラスの共通インターフェース

<?php
interface Printable
{
    public function setPrinterName(string $name);
    public function getPrinterName(): string;
    public function print(string $string);
}

PrinterProxyクラス

名前つきのプリンタを表すクラス(代理人)

<?php
class PrinterProxy implements Printable
{
    private $name;
    private $real;
    public function __construct(string $name)
    {
        $this->name = $name;
    }
    public function setPrinterName(string $name)
    {
        if ($this->real !== null) {
            $this->real->setPrinterName($name);
        }
        $this->name = $name;
    }
    public function getPrinterName(): string
    {
        return $this->name;
    }
    public function print(string $string)
    {
        $this->realize();
        $this->real->print($string);
    }
    private function realize()
    {
        if ($this->real === null) {
            $this->real = new Printer($this->name);
        }
    }
}

Printerクラス

名前つきのプリンタを表すクラス(本人)

<?php
class Printer implements Printable
{
    private $name;
    public function __construct(string $name)
    {
        $this->name = $name;
        $this->heavyJob("Printerのインスタンス(" . $this->name . ")を生成中");
    }
    public function setPrinterName(string $name)
    {
        $this->name = $name;
    }
    public function getPrinterName(): string
    {
        return $this->name;
    }
    public function print(string $string)
    {
        echo "=== {$this->name} ===" . PHP_EOL;
        echo $string;
    }
    private function heavyJob(string $msg)
    {
        echo $msg;
        for ($i = 0; $i < 5; $i++) {
            sleep(1);
            echo ".";
        }
        echo "完了." . PHP_EOL;
    }
}

Main

<?php
$p = new PrinterProxy("Alice");
echo "名前は現在" . $p->getPrinterName() . "です。" . PHP_EOL;
$p->setPrinterName("Bob");
echo "名前は現在" . $p->getPrinterName() . "です。" . PHP_EOL;
$p->print("Hello, world.");

実行結果

$ php Main.php
名前は現在Aliceです。
名前は現在Bobです。
Printerのインスタンス(Bob)を生成中.....完了.
=== Bob ===
Hello, world.#

ソースコード

github.com

役割

Subject役

Proxy役とRealSubject役を同一視するためのインターフェースを定める。 サンプルコードにおけるPrintableインターフェース

Proxy役

Proxy役はClient役からの要求をできるだけ処理する。もし自分だけで処理できなかったら、Proxy役はRealSubject役に仕事を任せる。 Proxy役は、ほんとうにRealSubject役が必要になってからRealSubject役を生成する。Proxy役はSubject役で定められているインターフェースを実装している。 サンプルコードにおけるPrinterProxyクラス。

RealSubject役

代理人のProxy役で手に負えなくなったときに登場するのが、本人のRealSubject役。 サンプルコードにおけるPrinterクラス。

Client役

Proxyパターンの利用者。 サンプルコードにおけるMain。

まとめ

  • 代理人を使うことで処理を高速化する
  • HTTPプロキシ
    • これもProxyパターンに当てはめて考えることができる。WebブラウザがClient、HTTPプロキシがProxy、WebサーバがRealSubject役。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門