橋渡しパターン(Bridge Pattern)は、ソフトウェア設計において、抽象化された部分と実装された部分を分離し、それぞれが独立して変更できるようにするためのデザインパターンです。これにより、システムの拡張性が向上し、保守が容易になります。
このパターンの必要性は、変化する要素が複数存在するシナリオで顕著になります。例えば、あるシステムが多様なハードウェア(PC、タブレット、スマートフォン)と、それらを動作させる複数のオペレーティングシステム(macOS、Windows、Linux)の両方に対応する必要があるとします。
従来の課題
もし、これらの組み合わせを継承関係だけで実装しようとすると、以下のような問題が発生します。
- クラス数の爆発: ハードウェアの種類とOSの種類が増えるごとに、それらの組み合わせ分のクラスを作成する必要があります。例えば、3種類のハードウェアと4種類のOSがあれば、12個のクラスが必要になります。
- 拡張性の低下: 新しいOS(例: HarmonyOS)が登場した場合、既存の全てのハードウェアクラスに対して、そのOSに対応する新しいクラスを追加しなければなりません。これは、特に大規模なシステムでは非常に手間がかかります。
橋渡しパターンの適用
橋渡しパターンでは、変化する要素(この例ではハードウェアとOS)をそれぞれ独立した階層として設計します。
-
抽象化された階層:
ハードウェアの種類を表す抽象クラス(例:
AbstractHardware)。 -
実装された階層:
OSの種類を表すインターフェース(例:
OperatingSystem)。
抽象化された階層のクラスは、実装された階層のインターフェースのインスタンスを保持(集約)します。これにより、ハードウェアクラスは特定のOS実装に依存することなく、OSの機能を利用できるようになります。
コード例
ハードウェア関連のクラス
// 抽象ハードウェアクラス
abstract class AbstractHardware {
protected OperatingSystem os; // OSインターフェースへの参照
// コンストラクタでOSを注入
protected AbstractHardware(OperatingSystem os) {
this.os = os;
}
// OSを実行する抽象メソッド
public abstract void execute();
}
// PCクラス
class PersonalComputer extends AbstractHardware {
public PersonalComputer(OperatingSystem os) {
super(os);
}
@Override
public void execute() {
String osInfo = os.runSystem();
System.out.println("PC上で実行: " + osInfo);
}
}
// スマートフォンクラス
class Smartphone extends AbstractHardware {
public Smartphone(OperatingSystem os) {
super(os);
}
@Override
public void execute() {
String osInfo = os.runSystem();
System.out.println("スマートフォン上で実行: " + osInfo);
}
}
// タブレットクラス (例)
class Tablet extends AbstractHardware {
public Tablet(OperatingSystem os) {
super(os);
}
@Override
public void execute() {
String osInfo = os.runSystem();
System.out.println("タブレット上で実行: " + osInfo);
}
}
オペレーティングシステム関連のクラス
// OSインターフェース
interface OperatingSystem {
String runSystem();
}
// macOS実装
class MacOS implements OperatingSystem {
@Override
public String runSystem() {
return "macOS";
}
}
// Windows実装
class WindowsOS implements OperatingSystem {
@Override
public String runSystem() {
return "Windows OS";
}
}
// Linux実装
class LinuxOS implements OperatingSystem {
@Override
public String runSystem() {
return "Linux OS";
}
}
// HarmonyOS実装 (新規追加)
class HarmonyOS implements OperatingSystem {
@Override
public String runSystem() {
return "HarmonyOS";
}
}
クライアント(使用例)
public class Client {
public static void main(String[] args) {
// PCでmacOSを実行
AbstractHardware pcOnMac = new PersonalComputer(new MacOS());
pcOnMac.execute();
// PCでLinuxを実行
AbstractHardware pcOnLinux = new PersonalComputer(new LinuxOS());
pcOnLinux.execute();
// スマートフォンでAndroidを実行 (ここではAndroidをOSインターフェースの実装として扱う)
AbstractHardware phoneOnAndroid = new Smartphone(new HarmonyOS()); // HarmonyOSを例として使用
phoneOnAndroid.execute();
// スマートフォンでmacOSを実行
AbstractHardware phoneOnMac = new Smartphone(new MacOS());
phoneOnMac.execute();
// 新しいOS (HarmonyOS) を追加する場合
System.out.println("\n--- HarmonyOS を追加 ---");
AbstractHardware pcOnHarmony = new PersonalComputer(new HarmonyOS());
pcOnHarmony.execute();
AbstractHardware phoneOnHarmony = new Smartphone(new HarmonyOS());
phoneOnHarmony.execute();
}
}
実行結果の例
PC上で実行: macOS
PC上で実行: Linux OS
スマートフォン上で実行: HarmonyOS
スマートフォン上で実行: macOS
--- HarmonyOS を追加 ---
PC上で実行: HarmonyOS
スマートフォン上で実行: HarmonyOS
橋渡しパターンの利点
- 独立した変更: ハードウェア層とOS層は互いに独立しているため、一方の変更がもう一方に影響を与えません。新しいOSが登場した場合でも、OS層のクラスを追加するだけで対応できます。
- 開発生産性の向上: 変更の影響範囲が限定されるため、開発やテストが容易になり、全体的な開発効率が向上します。
- オブジェクト指向原則の遵守: 「開閉原則(Open/Closed Principle)」を支持します。既存のコードを変更せずに、新しい機能(新しいOSなど)を追加できます。
このパターンの本質は、ビジネスロジックにおける複数の独立した変更軸を特定し、それらを疎結合にすることです。これにより、コードの柔軟性と拡張性が大幅に向上します。継承による強い結合よりも、集約(コンポジション)による柔軟な連携が、より適したアプローチとなる場面が多いことを示唆しています。