Interfaceと実装の中間装置的なデザインパターン。

既存のクラスに変更を加えることなく、APIを変更することができるのが利点。

FWとかに依存せずにusersテーブルからユーザ情報の一覧をとってくる

UML

重要なところ

どのような処理を行うかのInterfaceを定義する。

あとは、Userユーザデータを実際に取得するロジックを呼び出すような中間装置的な役割を持つAdapterを実装する。
以下は、CodeIgniterを利用した例

また、UserFetcharはCI_Db_query_builderクラスなどFW固有のクラスには一切依存していない。
これにより、クライアントコードをUserFetcherインターフェースに依存すれば、FWに依存せずユーザ情報が取得できる。

例えば、Laravelに依存する場合は以下のようにAdapterとFW固有のクラスが変わるだけ。

また、これらは委譲を行うパターンだが、継承を利用するパターンもある。

PHPで書いてみる

UserFetcher(Interface)

1
2
3
interface UserFetcher {
public function get() : array;
}

User(Entity)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User {
private $name;

public function __construct(array $config = [])
{
if (!empty($config['name'])) {
$this->name = $config['name']
}
}

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

Adapter(CodeIgniterのパターン)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Adapter implement UserFetcher
{
private $db;

public function __construct(CI_Db_query_builder $db)
{
$this->db = $db;
}

public function get(int $id) : array
{
reutrn $this->db->get('users')->result(User::class);
}
}

Client Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class NameRenderer {

private $fetcher;

public function __construct(UserFetcher $fetcher) // <- 抽象に依存
{
$this->fetcher = $fetcher;
}

public function render()
{
$users = $this->fetcher->get();
foreach ($users as $user) {
echo $user->getName() . 'さん、こんにちは';
}
}
}

$fetcher = new Adapter(get_instance()->db);
// NameRendererは、UserFetcherのインターフェースが利用できれば良い
$renderer = new NameRenderer($fetcher);
$renderer->render();

この様に、NameRendererがほしいUserFetcher Interfaceの中間装置的な役割を行う
Adapterを変更すれば、クライアントコードが実装可能になる。
また、これは委譲を行うパターンだが、継承を利用するパターンもある。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class TarouFamilyFactory {
public function createFather()
{
return new User(['name' => '一郎']);
}

public function createMother()
{
return new User(['name' => 'たえ']);
}

public function createBrother()
{
return new User(['name' => 'ロードリゲス']);
}

public function createTaro()
{
return new User(['name' => '太郎']);
}
}

Adapter(継承を利用するパターン)

1
2
3
4
5
6
7
8
9
10
11
12
13
class Adapter extends TarouFamilyFactory implement UserFetcher
{
public function get() : array
{
// 親クラスのメソッドを、Interfaceに合わせた形で提供する
return [
$this->createFather(), // 親クラスのメソッドを利用する
$this->createMother(),
$this->createBrother(),
$this->createTaro(),
];
}
}

まとめ

  1. Interface既存クラスの橋渡しを行う
  2. 委譲でも継承でも実装しても良い

参考

Wikipeida
デザインパターン入門 (Quick Tutorial)