【TS】今さら聞けないブリッジパターン

3 min読了の目安(約3100字TECH技術記事

はじめに

今回はブリッジパターン(Bridge Pattern)について解説します。
機能と実装を分離し、複数の方向にクラスを拡張できるようにするデザインパターンです。

ブリッジパターンとは?

TECHSCOREさんの解説があります。

以下、引用です。

第9章では Bridge パターンを学びます。Bridgeパターンとは、「Bridge」すなわち「橋」の役割を果たすパターンです。Bridgeパターンを利用することで、機能と実装を分離して、それぞれを独立に拡張することができるようになります。
bridge1
例えば、ある methodA というメソッドを持つクラス MyClassA は、methodA メソッドの実装が異なる MyClassASub1MyClassASub2 という2つのクラスによって継承されているとします。このとき、MyClassAmethodB というメソッドを追加するために、MyClassB クラスという MyClassA を継承するクラスを作成したことを考えてください。
bridge2
このとき、MyClassB でも、MyClassASub1MyClassASub2 で実装している methodA と同じ実装を利用したい場合、MyClassB クラスを継承する MyClassBSub1MyClassBSub2 といったクラスを作成する必要があります。

今回のように、2つのクラスだけなら手間はそんなにかかりませんが、場合によっては、MyClassA に機能を追加するためのサブクラス MyClassX を作成するたびに、何十という MyClassXSub・・・ というサブクラスを作成することが必要となります。Bridge パターンは、機能を拡張するための階層と実装を拡張するための階層を分離することにより、このようなわずらわしさを解消し、拡張を容易にするものです。

ポイントは 「機能を拡張するための階層と実装を拡張するための階層を分離」 するということです。
具体的な例を見ていきましょう。

例題

以下のように食器を表すクラスがあるとします。
食器クラスを継承した「金属製食器」と「木製食器」があり、
さらにそれらを継承した「金属製の箸」や「木製のスプーン」があるとします。

/**
 * 食器
 */
abstract class Dishware {
    use = () => {
        
    };
}

/**
 * 金属製食器
 */
class MetalDishware extends Dishware {

}

/**
 * 木製食器
 */
class WoodenDishware extends Dishware {

}

class MetalChopsticks extends MetalDishware {
    use = () => {
        console.log("hold");
    }
}

class WoodenChopsticks extends WoodenDishware {
    use = () => {
        console.log("hold");
    }
}

class MetalSpoon extends MetalDishware {
    use = () => {
        console.log("scoop");
    }
}

class WoodenSpoon extends WoodenDishware {
    use = () => {
        console.log("scoop");
    }
}

「食器」なので以降も「ガラス製」や「漆器」「プラスチック」など、様々な材質が増えていくかと思います。
さらに種類としても「フォーク」や「皿」「丼」などが追加されると、サブクラスは膨大な数になっていきます。

ブリッジパターンで実装

今回は「食器の材質」を切り出します。
MaterialImpleクラスを作成し、それを実装するMetalWoodを作成します。

abstract class MaterialImple {

}

class Metal extends MaterialImple {

}

class Wood extends MaterialImple {

}

Dishwareは上記のMaterialImpleを持ちます。

/**
 * 食器
 */
class Dishware {
    private _materialImple: MaterialImple;
    constructor(materialImple: MaterialImple) {
        this._materialImple = materialImple;
    }
    use = () => {

    }
}

さらにDishwareを継承したChopsticksSpoonを作成します。

class Chopsticks extends Dishware {
    use = () => {
        console.log("hold");
    }
}

class Spoon extends Dishware {
    use = () => {
        console.log("scoop");
    }
}

ポイントは「食器の材質」を切り出したことです。
こうすることで「食器」の「材質」と「種類」が分離され、それぞれの追加・実装が行いやすくなります。
例えば、プラスチックの材質を追加したければMaterialImpleを継承したPlasticを作成するだけで済み、
種類としてフォークを追加したければDishwareを継承したForkを作成するだけです。

以前の実装方法だと、上記に合わせてPlasticSpoonMetalForkといったサブクラスを作成する必要が出てきましたが、ブリッジパターンでは必要ありません。

まとめ

今回はTypescriptでデザインパターンの一つである Bridge Pattern について紹介しました。
特徴として「機能と実装」を分離することで、それぞれを独自に拡張できる点が挙げられます。
クラス構造が複雑になり、似たようなサブクラスを量産していると感じた場合には導入してみるといいかなと思います。