RitoLabo

Proxyパターン - PHPデザインパターン

  • 公開:
  • カテゴリ: PHP DesignPatterns
  • タグ: PHP,DesignPatterns,Structure,Proxy,VirtualProxy

Proxyパターン(プロキシ・パターン)は、構造に関するデザインパターン手法の1つで、主クラスの代理クラスを同一インターフェイスで実装する事で処理を委譲し、省リソース化や処理の取り回しを調整できる処理モデルです。

Proxyパターンの基本的なクラス図は以下になります。

Proxyパターンクラス図

実装例

今回は、画像の読み込みと表示についてをProxyパターンで実装します。Proxyパターンにはいくつかのパターンがあり、その中でもVirtualProxyパターンと言われる手法になります。

画像を取り扱う機能を作成する際に最低限必要なものとして「画像名」「画像パス」「画像データ」あたりを操作する機能にします。

まずは画像情報を保有するItemクラスを作成します。

App/Item/Image.php
<?php

namespace App\Item;

class Image
{
private $name;
private $path;

public function __construct($name, $path)
{
$this->name = $name;
$this->path = $path;
}

public function getName()
{
return $this->name;
}

public function getPath()
{
return $this->path;
}
}

画像名(題名)と画像パスを持つシンプルなものになっています。

次にSubjectクラスです。インターフェイスを定義します。

App/Interfaces/ImageInterface.php
<?php

namespace App\Interfaces;

interface ImageInterface
{
public function ImageInfo();
public function ImageDisplay();
}
  • ImageInfo() メソッドは、画像情報を出力します。
  • ImageDisplay() メソッドは、画像を表示します。

次はRealSubjectクラスです。「本人」の役割を持つクラスです。

App/Subject/RealImage.php
<?php

namespace App\Subject;

use App\Interfaces\ImageInterface;
use App\Item\Image;

class RealImage implements ImageInterface
{
private $image;
private $content;

public function __construct(Image $image)
{
$this->image = $image;
$this->content = base64_encode(file_get_contents($image->getPath()));
}

public function ImageInfo()
{
echo sprintf("画像名:%s<br>画像パス:%s", $this->image->getName(), $this->image->getPath());
}

public function ImageDisplay()
{
echo sprintf("<img src='data:image/png;base64,%s' width='300' height='auto'>", $this->content);
}
}

ここでは、コンストラクタで画像オブジェクトを受け取り、画像情報と、画像の実態の読み込みを行っています。

そして、インターフェイスを実装しています。ImageInfo() メソッドでは画像情報の出力を、ImageDisplay() メソッドでは画像自体の表示を行っています。

最後に、Proxyクラスです。「代理人」の役割を持つクラスになります。

App/Proxy/ProxyImage.php
<?php

namespace App\Proxy;

use App\Interfaces\ImageInterface;
use App\Item\Image;
use App\Subject\RealImage;

class ProxyImage implements ImageInterface
{
private $image;

public function __construct(Image $image)
{
$this->image = $image;
}

public function ImageInfo()
{
echo sprintf("画像名:%s<br>画像パス:%s", $this->image->getName(), $this->image->getPath());
}

public function ImageDisplay()
{
$real = new RealImage($this->image);
$real->ImageDisplay();
}
}

ここでのポイントはImageDisplay() メソッドです。RealImageオブジェクトをインスタンス化し、そこのImageDisplay()メソッドを用いて画像表示を行っています。

実装が完了したので、クライアントから利用してみます。

index.php
<?php

use App\Item\Image;
use App\Proxy\ProxyImage;

$image = new ProxyImage(new Image('サンプル画像', 'images/sample.jpg'));

// 画像情報の出力
$image->ImageInfo();

echo '<hr>';

// 画像の表示
$image->ImageDisplay();

結果は以下になります。

PHPでのProxyパターン結果画面

これらが結局何なのかというと、結果で写っている画像は実は12MB弱ある大きな画像を読み込んでいます。

この時にRealSubjectクラスでは、インスタンス化を行う際に画像データを読み込み表示に対応するので、大きなリソースを読み込む事で処理に時間がかかる事になり、それなりのサーバリソースも消費します。

これが、必ず画像を表示するような場面であればProxyパターンは不要ですが、もし画像の情報は必要だが画像の表示自体は不要である場面もある場合はどうでしょうか。その時は読み込む必要はありません。

その場合はVirtualProxyパターンを用いる事で、Proxyクラスでは画像データは読み込まずに処理する事ができ、処理にかかる時間やリソースを大幅に短縮できます。

実際に、クライアントコードの中の以下の部分

// 画像の表示
$image->ImageDisplay();

これがある場合は処理完了までに2秒弱かかりますが、これが無い場合は一瞬で完了します。つまり画像表示が不要な場面であればProxyImageクラスで取り回した方が圧倒的に処理時間や消費リソースが少なくて済むのです。

まとめ

実装例はつまりは遅延ロードであり、本人役のコア処理が「不要」な時こそ力を発揮するパターンですが、本当に必要になった時にそのリソースを読み込む事で処理を省力化する事が出来ます。Proxyパターンはまさしくこういった手法も思想の1つとして持っています。「Proxy = 代理人」を用いる利点の1つです。

Proxyパターンには他にも「remote proxyパターン」「Protection Proxyパターン」「smart referenceパターン」などがあるので是非試してみてください。