PHPで使うGoFパターン ひとり Advent Calendar - プロキシ


前回予告したように今回はプロキシです。今回は前回のような説教臭い話ではないので素直に読んでいただければと思います。

## プロキシってなに?
要するに包含して拡張するパターンです。包含するのが別クラスのオブジェクトとは限らなくて、やや抽象的なパターンですが。敢えて、理解し易くするために身も蓋もない言い方をすれば、継承の代わりにオブジェクトを内包して、その内包したオブジェクトのメソッドのラッパメソッドを実装するみたいなモノです。
プロキシパターンの中にも更にパターンがあって、包含の部分で包含の仕方を工夫したり、プロキシ側がラップしているインターフェース部分を工夫したりすることで様々な事が出来るのが特徴です。代表的なプロキシパターンは以下の通りです。

* アクセス制御プロキシ - 各ラッパメソッドの中にvalidate処理を追加して安全に実メソッドへアクセスさせる。(これは個人的に継承しても良かったのでは?とも思います)
* バーチャルプロキシ - 作成するコストが高いリソースの生成を可能な限り遅らせる
* リモートプロキシ - これは必ずしも拡張対象のオブジェクトを包含して拡張する訳じゃないです。例えばAPIのラッパクラスのようなもの。Facebook SDKとか。

どうせならプロキシパターンの各々の実装例を挙げた方が良いのですが、今回の目的からズレ過ぎるので(前回もズレたし)、今回はやりません。一番基本的なパターンのみとします。

## プロキシの構造

* ISubject：プロキシされる側のインターフェース
* RealSubject：実際に処理をする実体。Subjectを実装する。Proxyの存在は知らない。
* Proxy：アクセスを受け付ける代理。
RealSubjectへの参照を持つ。RealSubjectのインスタンス化処理を行うこともある。


```php
<?php
interface ISubject {
  public function doMethod1 ( );
  public function doMethod2 ( );
  public function doMethod3 ( );
}

class Proxy {
  protected $_Subject = null;
  public function __construct ( $sbj ) {
    if ( !( $sbj instanceof ISubject ) )
      throw new Exception ( 'こんなオブジェクト無理だもん!' );
    $this->_Subject = $sbj;
  }
  public function doMethod1 () { $this->_Subject->doMethod1 (); }
  public function doMethod2 () { $this->_Subject->doMethod2 (); }
  public function doMethod3 () { $this->_Subject->doMethod3 (); }
}

class RealSubject1 implements ISubject {
  public function doMethod1 () { print "1の1\n"; }
  public function doMethod2 () { print "1の2\n"; }
  public function doMethod3 () { print "1の3\n"; }
}

class RealSubject2 implements ISubject {
  public function doMethod1 () { print "2の1\n"; }
  public function doMethod2 () { print "2の2\n"; }
  public function doMethod3 () { print "2の3\n"; }
}

$proxy1 = new Proxy ( new RealSubject1 () );
$proxy2 = new Proxy ( new RealSubject2 () );

$proxy1->doMethod1();
$proxy2->doMethod1();
$proxy1->doMethod2();
$proxy2->doMethod2();
$proxy1->doMethod3();
$proxy2->doMethod3();

$proxy1 = null;
$proxy2 = null;
```

これを以下のように変えても使う側からは何も意識する必要ないですね。デストラクタで色々すんな! ってのは一旦置いておいて。勿論これは悪い見本ですが、引数と処理するメソッド名をセットで保持しておいて、UIから操作を受けとった時には処理を記録するだけにしておき、実操作(特にネットワークを介した処理など、時間のかかる場合は有効)は、ユーザーに意識させないところで、まとめて行なうことも出来るというのは伝わったのではないかと思います。

```php
<?php
interface ISubject {
  public function doMethod1 ( );
  public function doMethod2 ( );
  public function doMethod3 ( );
}

class Proxy {
  protected $_Subject = null;
  protected $_Commands = array();
  public function __construct ( $sbj ) {
    if ( !( $sbj instanceof ISubject ) )
      throw new Exception ( 'こんなオブジェクト無理だもん!' );
    $this->_Subject = $sbj;
  }
  public function doMethod1 () { $this->_Commands[] = 'doMethod1'; }
  public function doMethod2 () { $this->_Commands[] = 'doMethod2'; }
  public function doMethod3 () { $this->_Commands[] = 'doMethod3'; }
  public function __destruct () {
    foreach ( $this->_Commands as $method ) $this->_Subject->$method ();
    $this->_Subject = null;
  }
}

class RealSubject1 implements ISubject {
  public function doMethod1 () { print "1の1\n"; }
  public function doMethod2 () { print "1の2\n"; }
  public function doMethod3 () { print "1の3\n"; }
}

class RealSubject2 implements ISubject {
  public function doMethod1 () { print "2の1\n"; }
  public function doMethod2 () { print "2の2\n"; }
  public function doMethod3 () { print "2の3\n"; }
}
$proxy1 = new Proxy ( new RealSubject1 () );
$proxy2 = new Proxy ( new RealSubject2 () );
$proxy1->doMethod1();
$proxy2->doMethod1();
$proxy1->doMethod2();
$proxy2->doMethod2();
$proxy1->doMethod3();
$proxy2->doMethod3();
```