1. Home
  2. PHP
  3. DesignPatterns
  4. Bridgeパターン - PHPデザインパターン

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

  • 公開日
  • 更新日
  • カテゴリ:DesignPatterns
  • タグ:PHP,DesignPatterns,Structure,Bridge
Bridgeパターン - PHPデザインパターン

Bridge パターン(ブリッジ・パターン) は、構造に関するデザインパターン手法の1つで、ブリッジ=橋、つまり「橋渡し」を行うクラスを挟む事で構造の効率化を図る事のできる処理モデルです。

「橋」をイメージしてみます。 A 地点と B 地点にかかる橋はもちろん横に伸びているはずです。縦に伸びているなんて、橋とは呼べません。(それってむしろ壁)

Bridge パターンはまさにこの「横につなぐこと」がイメージとしては最も大切な要素の1つです。

このデザインパターン手法を紹介する時には良く「継承」が引き合いに出されます。例えば、ワインを扱う EC サイトのアプリケーションのクラス群を考えてみます。継承を利用してこんな構成を考えました。

ワインについて、赤と白を継承した国別のワインクラスがあります。赤と白それぞれに三ヵ国ずつぶら下がっていますが、もしここに「カルフォルニアワイン」を増やそうと思ったら、赤側と白側に合計2つのクラス追加が必要です。

まだそれだけで済めばよいですが、もし「ロゼワインも扱おう」となれば、赤と白と同じだけのクラスを「ロゼ用」として作成しなければなりません。つまり追加が倍々になっていきます。そしてコードの重複が発生します。

そうやっていくうちに取り扱う国もクラスも増え続け、商売も上向きになってきた頃にオーナーがこう言いました。
もっとお客さんにわかりやすいように、「フランス」ではなく、「ボルドー(フランス)」「ブルゴーニュ(フランス)」のように、全ての国を産地で切っていこうよ。

こうなるともうカオスです。継承が深くなると、こういう状況が生まれます。

そこで満を持して登場するのがこの「Bridge パターン」です。

冒頭で述べた通り、Bridge パターンは橋渡し、つまり「横」を繋ぐことによって構造を最適化できるデザインパターンです。

先ほどのワインの構造を Bridge パターンで表現すると、以下のような関係になります。

画で見ると結構イメージもわきやすいです。ワインの種類と産地で分類し、それらを橋渡しすることでワインのバリエーションを形成します。これなら産地にカルフォルニアを足そうが、種類にロゼを増やそうが変更は容易です。コードの重複も起こりません。

Bridge パターンとは、こういった構造を最適化できる、優れたデザインパターン手法になります。

Bridge パターンの基本的なクラス図は以下の通りです。

実装例

今回はあるアプリケーションがデータを取得し表示する例で実装していきます。

このアプリケーションでは、あるページで連続したデータを持つ配列を取得し表示していますが、表示箇所が不規則な為、一つずつ、任意の場所へ表示する必要がありました。。ですがこの度、別の箇所で同じデータを用いてループで表示する処理が必要になりました。

まずは、データを取り出すクラスから作成します。インターフェイスを定義します。今回はデモなのでデータベースからではなく json ファイルをデータソースとし、データを取得します。

App/Bridge/Interfaces/FileDataManageInterface.php
<?php

namespace App\Bridge\Interfaces;

interface FileDataManageInterface
{
    public function read();
    public function display();
    public function getTotalCount();
    public function getPointer();
}

それぞれ、ファイルを読み込む、表示する、データ個数、現在のポインタ位置を返却するメソッドを定義しています。このインターフェイスは、クラス図でいうところの Implementor に当たります。

次に、このインターフェイスを実装し具象クラスを作成します。

App/Bridge/FileDataManage.php
<?php

namespace App\Bridge;

use App\Bridge\Interfaces\FileDataManageInterface;

class FileDataManage implements FileDataManageInterface
{
    private $json_path;
    private $data;
    private $data_total = 0;
    private $pointer = 0;

    public function __construct($json)
    {
        $this->json_path = $json;
    }

    public function read()
    {
        $this->data = json_decode(file_get_contents($this->json_path));
        $this->data_total = count($this->data);
    }

    public function display()
    {
        echo sprintf(
            "id:%s / name:%s / category:%s<br>",
            $this->data[$this->pointer]->id,
            $this->data[$this->pointer]->name,
            $this->data[$this->pointer]->category
        );
        $this->pointer++;
    }

    public function getTotalCount()
    {
        return $this->data_total;
    }

    public function getPointer()
    {
        return $this->pointer;
    }
}

ここでの機能は最も基本的なものとなります。クラス図で言うところの ConcreteImplementor です。そしてここまでで、データを取得する側、つまりは A 地点の完成です。

次は、データを表示する側、B 地点を作成します。まずは初期に実装していた、一つずつ、任意の場所へ表示するクラスです。

App/Bridge/Output.php
<?php

namespace App\Bridge;

class Output
{
    protected $data_manager;

    public function __construct(FileDataManage $dataManage)
    {
        $this->data_manager = $dataManage;
    }

    public function read()
    {
        $this->data_manager->read();
    }
    public function display()
    {
        $this->data_manager->display();
    }
}

ポイントはデータ周りを担当するクラスを継承ではなく委譲で受け取っている点です。ここがいわゆる「橋渡し」に部分になりますが、この例ではこうすることで、データの提供側と表示側が完全に分離しています。そして、コストラクタで受け取ったオブジェクトを利用し、読み込みと表示メソッドをそのまま実行するように定義しています。

次に、データの表示方法が増えた部分、ループでの表示処理が必要な場面が出てきた部分です。

App/Bridge/OutputAuto.php
<?php

namespace App\Bridge;

use App\Bridge\Output;

class OutputAuto extends Output
{
    public function __construct(FileDataManage $dataManage) {
        parent::__construct($dataManage);
    }

    public function autoDisplay()
    {
        while ($this->data_manager->getPointer() !== $this->data_manager->getTotalCount()) {
            $this->data_manager->display();
        }
    }
}

初代のクラスを継承した上で、今回必要な処理を定義しています。 ここでも、データ取得係のクラスは委譲で受け取っています。

最後に、これらを使って実際に表示を行います。

index.php
<?php
use App\Bridge\FileDataManage;
use App\Bridge\Output;
use App\Bridge\OutputAuto;

$json_path = 'App/Files/frameworks.json';

$manual = new Output(new FileDataManage($json_path));
$auto = new OutputAuto(new FileDataManage($json_path));

$manual->read();
$auto->read();

echo '<h2>1つずつ表示</h2>';
$manual->display();
$manual->display();
$manual->display();

echo '<h2>全て表示</h2>';
$auto->autoDisplay();

結果は以下のようになります。

まとめ

実装例は冒頭で紹介した例よりはかなり簡易的だったので Bridge パターンによる恩恵はあまり大きくなかったですが、それでもここから規模を大きくしていくにつれてこのパターンで良かったと思えるでしょう。

例えばこうしておくことで、データの取得を json からデータベースに切り替える際も容易です。なぜなら橋渡しのおかげで、役割間の依存が無いからです。

このように、Bridge パターンを用いると提供側と表示側それぞれで拡張を容易に行えるようになります。何かを追加や拡張する際にどこかにも余計にクラスを作成したりする必要がなくなるので、アプリケーションの規模が大きくなればなるほどその恩恵に肖る事が出来ます。コードの重複も防げます。

とはいえ、Bridge パターンというデザインパターンの1つはそれはそれとして、クラス設計時にはこうした構造に関してきちんと計画して実装したいものです。それを気づかせてくれるのも、デザインパターンならではです。パターンだけあって、Java だろうが PHP だろうが、きちんとはまるあたりが先人の知恵とは素晴らしい。

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級