RitoLabo

Adapterパターン | PHPデザインパターン

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

Adapterパターンは、機能の再利用の際にで用いられるデザインパターンの手法の1つです。互換性のないインターフェースを持つクラスやそれらの機能を吸収するクラス(=アダプタ)を設けて両者を吸収する事で、既に存在しているクラスを変更する事なく新しい機能を持ったクラスを定義できます。(Wrapper(ラッパー)パターンとも呼ばれます)

アダプター、つまりは両者を連絡するという意味の通り、これまで別々に存在し使われていた機能をつなぎ合わせ、一つの処理モデルを定義する事が出来ます。そしてそれは、再利用する機能を再利用用に変更するのではなく、それらをつなぎ合わせ1つのクラスを作成する事で、従来のものを変更せずに済む事になります。

Adapterパターンには、以下2つの手法があります。

継承を用いるAdapterパターン

アダプタで吸収する機能がサブクラスとインターフェイスの場合に用いられます。アダプタとして作成したクラスが拡張するクラスを継承し、そして取り込む一方のインターフェイスを実装します。

継承を用いるAdapterパターンのクラス図

委譲を用いるAdapterパターン

アダプタとして作成したクラスが、拡張するクラスを直接インスタンス化し、そして取り込む一方のインターフェイスを実装します。

委譲を用いるAdapterパターンのクラス図

こちらは主に、アダプタで吸収する機能のうち、拡張として吸収する側のクラスが多重継承や継承不可クラスであった場合に用いられます。

実装例

Adapterパターンの実装例を以下に示します。

あるところに「PHP自動車株式会社(以下、PHP自動車)」という会社がありました。人気車種「Laravel」を主軸にたくさんの車種を世界展開していました。

「わが社の人気車種 Laravel は、ガソリンの燃焼効率を最大限にまで向上させる事で高燃費を実現しています!」

ガソリン車(Automobile)であるLaravelの持つ機能は以下の通りです。

App/Car/Automobile.php
<?php
namespace App\Car;

use App\Car\Interfaces\gasolineEngineInterface;

class Automobile implements gasolineEngineInterface
{
public function gasolineOutput($ratio)
{
return sprintf('ガソリン:%d ', $ratio);
}

public function running()
{
echo sprintf('[出力] %s', $this->gasolineOutput(100));
}

}

通常のガソリン車であるLaravelは、インターフェイスにガソリンでの出力を司る gasolineOutput()メソッド を兼ね備えています。

App/Car/Interfaces/gasolineEngineInterface.php
<?php
namespace App\Car\Interfaces;

interface GasolineEngineInterface
{
public function gasolineOutput($ratio);
}

これらを用いて発進する事で、ガソリン出力を得る事が出来ます。

<?php
use App\car\Automobile;

$automobile = new Automobile();
$automobile->running();

=> ガソリン:100 %

しかしながら、時代の流れとともに自動車産業にも環境保全の波が押し寄せてきました。

そこでPHP自動車は研究に研究を重ね、電気自動車の開発に成功しました。

「わが社の新型車 CakePHP は、電気で走るこれまでとは全く違う自動車です。これにより、環境にやさしく先進的な体験をみなさんに提供いたします!」

電気自動車(ElectricCar)であるCakePHPの持つ機能は以下の通りです。

App/Car/ElectricCar.php
<?php
namespace App\Car;

use App\Car\Interfaces\ElectricEngineInterface;

class ElectricCar implements ElectricEngineInterface
{
public function electricOutput($ratio)
{
return sprintf('電気:%d ', $ratio);
}

public function running()
{
echo sprintf('[出力] %s', $this->electricOutput(100));
}
}

電気自動車なので、インターフェイスに電気での出力を司る electricOutput()メソッドを装備しています。

App/Car/Interfaces/ElectricEngineInterface.php
<?php
namespace App\Car\Interfaces;

interface ElectricEngineInterface
{
public function electricOutput($ratio);
}

このポテンシャルを以て、電気出力での走行が可能になります。

<?php
use App\Car\ElectricCar;

$electric_car = new ElectricCar();
$electric_car->running();

=> 電気:100 %

電気自動車は大成功。性能もよく飛ぶように売れました。

しかし、社長には気がかりな事がありました。それは、ガソリン車であるLaravelの事です。

「ううむ。。わが社の代名詞ともいうこの車を、時代の流れで衰退させるわけにはいかぬ。どうにか刷新を図りたい。」

そこで素晴らしいアイデアを考え付きました。

「そうだ、ガソリンと電気の両方を使うハイブリッド車を作ろう!ガソリン車自体はまだまだ需要はあるとはいえ、時代の流れを鑑みて、ガソリンと電気の良いところを合わせれば環境にもやさしく力強い走りを実現できる!」

PHP自動車の顔ともいうべきLaravelの新型開発が始まりました。

Laravel新モデルの作成に際してとりまとめたメモが以下のように残っていました。

  • 電気出力には、CakePHPに搭載されているわが社の先進的な電気出力機能を使う。
  • ガソリン車としてのLaravelはまだまだこの先も独自の発展を遂げていく。だからガソリン仕様車は残した上で、ニューモデルであるハイブリッド車を作る。

従来(ガソリン)車は残したまま、それでいて電気要素も装備した新しい新車を作る。もちろん従来車がアップデートされれば新車にもそれは適用される。

この課題を受けて技術者たちは考えました。
「ううむ。。できるだけいいとこどりをして無駄のないようにしなければいけないな。」

そこで用いたのがAdapterパターンでした。

「ガソリンLaravelを継承した新しいクラスを作り、そこに電気出力インターフェイスを装備する。こうする事でハイブリッドLaravelが出来るはずだ!」

ハイブリッド車(HybridCar)であるLaravel Hybridの持つ機能は以下の通りです。

App/Car/HybridCar.php
<?php
namespace App\Car;

use App\Car\Interfaces\ElectricEngineInterface;

class HybridCar extends Automobile implements ElectricEngineInterface
{
public function electricOutput($ratio)
{
return sprintf('電気:%d ', $ratio);
}

public function running()
{
// ガソリン出力
echo sprintf('[出力] %s', $this->gasolineOutput(50));
// 電気出力
echo sprintf('[出力] %s', $this->electricOutput(50));
}

}

同車種であるガソリン車を継承し、異車種である電気自動車の電気出力インターフェイスを装備しています。

App/Car/Interfaces/ElectricEngineInterface.php
<?php
namespace App\Car\Interfaces;

interface ElectricEngineInterface
{
public function electricOutput($ratio);
}

これらを用いてサブクラスでは、ハイブリッドでの動作の為の定義やオーバーライドを行い、ハイブリッド車としての新型モデルLaravel Hybridが誕生しました。

<?php
use App\Car\HybridCar;

$hybrid_car = new HybridCar();
$hybrid_car->running();

=> ガソリン:50 % 電気:50 %

ちなみにこの例は継承を用いたAdapterパターンですが、委譲を用いた手法の場合は、HybridCarクラスは以下のようになります。

<?php
namespace App\Car;

use App\Car\Interfaces\ElectricEngineInterface;

class HybridCar implements ElectricEngineInterface
{
protected $automobile;

public function __construct()
{
$this->automobile = new Automobile();
}

public function electricOutput($ratio)
{
return sprintf('電気:%d ', $ratio);
}

public function running()
{
// ガソリン出力
echo sprintf('[出力] %s', $this->automobile->gasolineOutput(50));

// 電気出力
echo sprintf('[出力] %s', $this->electricOutput(50));
}

}

まとめ

Adapterパターンを用いると、コードの重複も無くせる上に母体がアップデートすればアダプタで生成した側の機能も同時にアップデートされ、効率良く処理モデルを構築していけます。

また、1つの処理モデルを作成する際に、基となるクラスやインターフェイスは一切変更を加えなくて済むというのも大きな利点です。

ただしこの手法を使いまくると各機能の関係性が不透明になる場合も多くあるので、もし意図せず多用せざるを得ない状況になった場合は設計を見直す事が大切です。見通しの良い設計の元で計画的に用いていきましょう。