【TS】今さら聞けないブリッジパターン
はじめに
今回はブリッジパターン(Bridge Pattern
)について解説します。
機能と実装を分離し、複数の方向にクラスを拡張できるようにするデザインパターンです。
ブリッジパターンとは?
TECHSCORE
さんの解説があります。
以下、引用です。
第9章では
Bridge
パターンを学びます。Bridge
パターンとは、「Bridge
」すなわち「橋」の役割を果たすパターンです。Bridge
パターンを利用することで、機能と実装を分離して、それぞれを独立に拡張することができるようになります。
例えば、あるmethodA
というメソッドを持つクラスMyClassA
は、methodA
メソッドの実装が異なるMyClassASub1
、MyClassASub2
という2つのクラスによって継承されているとします。このとき、MyClassA
にmethodB
というメソッドを追加するために、MyClassB
クラスというMyClassA
を継承するクラスを作成したことを考えてください。
このとき、MyClassB
でも、MyClassASub1
、MyClassASub2
で実装しているmethodA
と同じ実装を利用したい場合、MyClassB
クラスを継承するMyClassBSub1
、MyClassBSub2
といったクラスを作成する必要があります。今回のように、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
クラスを作成し、それを実装するMetal
とWood
を作成します。
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
を継承したChopsticks
とSpoon
を作成します。
class Chopsticks extends Dishware {
use = () => {
console.log("hold");
}
}
class Spoon extends Dishware {
use = () => {
console.log("scoop");
}
}
ポイントは「食器の材質」を切り出したことです。
こうすることで「食器」の「材質」と「種類」が分離され、それぞれの追加・実装が行いやすくなります。
例えば、プラスチックの材質を追加したければMaterialImple
を継承したPlastic
を作成するだけで済み、
種類としてフォークを追加したければDishware
を継承したFork
を作成するだけです。
以前の実装方法だと、上記に合わせてPlasticSpoon
やMetalFork
といったサブクラスを作成する必要が出てきましたが、ブリッジパターンでは必要ありません。
まとめ
今回はTypescript
でデザインパターンの一つである Bridge Pattern
について紹介しました。
特徴として「機能と実装」を分離することで、それぞれを独自に拡張できる点が挙げられます。
クラス構造が複雑になり、似たようなサブクラスを量産していると感じた場合には導入してみるといいかなと思います。
Discussion