0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

全30回:静的と動的でどう違うのか、JavaとPythonで学ぶデザインパターン - Day 10 Bridgeパターン:実装と抽象を分離する

Posted at

はじめに

皆さん、こんにちは!「JavaとPythonで比べるデザインパターン」シリーズの第10回目です。
今回は、抽象部分と実装部分を分離し、それぞれを独立して拡張できるようにするBridge(ブリッジ)パターンについて解説します。


Bridgeパターンとは?

Bridgeパターンは、クラスの抽象実装を分離し、両者を**橋渡し(ブリッジ)**するデザインパターンです。これにより、両者を独立して変更・拡張できます。このパターンは、複数の軸でクラス階層を増やしたい場合に特に有効です。

例えるなら、リモコンテレビの関係です。
リモコン(抽象)はチャンネル変更や音量調整といった機能(抽象メソッド)を提供しますが、その内部でソニーやパナソニックといった特定のテレビ(実装)に依存していません。リモコンとテレビは、通信プロトコル(ブリッジ)を介して連携しており、リモコンの買い替えやテレビの買い替えをしても、互いに影響を与えずに済みます。

Bridgeパターンが解決する問題

従来のアプローチで図形を実装する場合を考えてみましょう。形(Circle、Square)と色(Red、Blue)の組み合わせを継承で表現しようとすると、以下のような問題が発生します:

Shape
├── RedCircle
├── BlueCircle
├── RedSquare
└── BlueSquare

新しい色(Green)や形(Triangle)を追加するたびに、クラスの数が組み合わせ爆発的に増加してしまいます。これが「クラス爆発」問題です。

Bridgeパターンでは、この問題を「形」と「色」という2つの独立した階層に分離することで解決します。

パターンの構成要素

  1. 抽象(Abstraction): クライアントが利用する高レベルのインターフェースを定義し、実装オブジェクトへの参照を保持します
  2. 拡張抽象(RefinedAbstraction): 抽象を継承し、クライアントに具体的な機能を提供します
  3. 実装者インターフェース(Implementor): 実装オブジェクトの共通インターフェースを定義します
  4. 具象実装者(ConcreteImplementor): 実装者インターフェースを実装し、具体的な機能を提供します

Javaでの実装:厳格なインターフェースの分離

Javaは静的型付け言語であり、インターフェースと抽象クラスを使って抽象と実装を厳密に分離します。

以下に、図形(形と色)を描画する例を示します。

// 実装者インターフェース (Implementor)
interface Color {
    void applyColor();
}

// 具象実装者 (ConcreteImplementor)
class RedColor implements Color {
    @Override
    public void applyColor() {
        System.out.println("Applying red color.");
    }
}

class BlueColor implements Color {
    @Override
    public void applyColor() {
        System.out.println("Applying blue color.");
    }
}

class GreenColor implements Color {
    @Override
    public void applyColor() {
        System.out.println("Applying green color.");
    }
}

// 抽象 (Abstraction)
abstract class Shape {
    protected Color color;

    public Shape(Color color) {
        this.color = color;
    }

    public abstract void draw();
    
    // 共通の機能も提供できる
    public void changeColor(Color newColor) {
        this.color = newColor;
    }
}

// 拡張抽象 (RefinedAbstraction)
class Circle extends Shape {
    public Circle(Color color) {
        super(color);
    }
    
    @Override
    public void draw() {
        System.out.print("Drawing a Circle. ");
        color.applyColor();
    }
}

class Square extends Shape {
    public Square(Color color) {
        super(color);
    }

    @Override
    public void draw() {
        System.out.print("Drawing a Square. ");
        color.applyColor();
    }
}

class Triangle extends Shape {
    public Triangle(Color color) {
        super(color);
    }

    @Override
    public void draw() {
        System.out.print("Drawing a Triangle. ");
        color.applyColor();
    }
}

// 使用例
public class BridgePatternDemo {
    public static void main(String[] args) {
        // 様々な形と色の組み合わせ
        Shape redCircle = new Circle(new RedColor());
        redCircle.draw(); // "Drawing a Circle. Applying red color."

        Shape blueSquare = new Square(new BlueColor());
        blueSquare.draw(); // "Drawing a Square. Applying blue color."
        
        Shape greenTriangle = new Triangle(new GreenColor());
        greenTriangle.draw(); // "Drawing a Triangle. Applying green color."
        
        // 実行時に色を変更することも可能
        redCircle.changeColor(new GreenColor());
        redCircle.draw(); // "Drawing a Circle. Applying green color."
    }
}

この実装では、Shape(抽象)とColor(実装)が完全に分離されており、新しい形や色を追加しても、互いに影響を与えずに済みます。


Pythonでの実装:柔軟なクラスの組み合わせ

Pythonの動的型付けと柔軟なクラス構造は、Bridgeパターンをよりシンプルに実装できます。厳格なインターフェースを定義しなくても、オブジェクトの合成によって同じ目的を達成できます。

from abc import ABC, abstractmethod

# 実装者の基底クラス (Implementor)
class ColorImplementor(ABC):
    @abstractmethod
    def apply_color(self):
        pass

# 具象実装者 (ConcreteImplementor)
class RedColor(ColorImplementor):
    def apply_color(self):
        print("Applying red color.")

class BlueColor(ColorImplementor):
    def apply_color(self):
        print("Applying blue color.")

class GreenColor(ColorImplementor):
    def apply_color(self):
        print("Applying green color.")

# 抽象 (Abstraction)
class Shape(ABC):
    def __init__(self, color_impl):
        self._color_impl = color_impl

    @abstractmethod
    def draw(self):
        pass
    
    def change_color(self, new_color_impl):
        """実行時に色の実装を変更"""
        self._color_impl = new_color_impl

# 拡張抽象 (RefinedAbstraction)
class Circle(Shape):
    def __init__(self, color_impl):
        super().__init__(color_impl)

    def draw(self):
        print("Drawing a Circle. ", end="")
        self._color_impl.apply_color()

class Square(Shape):
    def __init__(self, color_impl):
        super().__init__(color_impl)

    def draw(self):
        print("Drawing a Square. ", end="")
        self._color_impl.apply_color()

class Triangle(Shape):
    def __init__(self, color_impl):
        super().__init__(color_impl)

    def draw(self):
        print("Drawing a Triangle. ", end="")
        self._color_impl.apply_color()

# 使用例
def main():
    # 様々な形と色の組み合わせ
    red_circle = Circle(RedColor())
    red_circle.draw()  # "Drawing a Circle. Applying red color."

    blue_square = Square(BlueColor())
    blue_square.draw()  # "Drawing a Square. Applying blue color."
    
    green_triangle = Triangle(GreenColor())
    green_triangle.draw()  # "Drawing a Triangle. Applying green color."
    
    # 実行時に色を変更
    red_circle.change_color(GreenColor())
    red_circle.draw()  # "Drawing a Circle. Applying green color."

if __name__ == "__main__":
    main()

Pythonのこの実装では、Shapeクラスがcolor_implを保持し、メソッド内でその実装を呼び出すことで、オブジェクトの合成によって抽象と実装を分離しています。ABCモジュールを使用することで、より明確なインターフェースを定義できます。


Bridgeパターンの利点と注意点

利点

  1. 抽象と実装の独立性: 抽象クラスと実装クラスを独立して開発・変更できます
  2. 実行時の実装切り替え: オブジェクト生成後も実装を変更できます
  3. クラス爆発の回避: 組み合わせの数だけクラスを作る必要がありません
  4. 開放閉鎖の原則: 既存のコードを変更せずに新しい抽象や実装を追加できます

注意点

  1. 複雑性の増加: 単純なケースでは過度に複雑になる可能性があります
  2. 理解の困難さ: 抽象と実装の分離は初心者には理解が困難な場合があります

実践的な使用例

Bridgeパターンは以下のような場面で特に有効です:

  • GUI フレームワーク: プラットフォーム固有の実装(Windows、Mac、Linux)から抽象的なUI要素を分離
  • データベースアクセス: 異なるデータベース(MySQL、PostgreSQL、Oracle)に対する共通のインターフェースを提供
  • デバイス制御: 異なるデバイス(プリンタ、ディスプレイ)に対する統一的な制御インターフェース

まとめ:本質は「クラスの独立した拡張」

特性 Java Python
主な解決策 厳格なインターフェースと抽象クラス ABCとオブジェクトの合成
設計思想 抽象と実装を継承から分離する 継承よりも合成を優先する
コードの意図 型を通じて分離の意図を明示 実行時のオブジェクトの振る舞いで分離を実現
柔軟性 コンパイル時に型チェック 実行時により柔軟な変更が可能

Bridgeパターンは、両言語で実装方法が異なりますが、**「複数の次元でクラスを独立して拡張できる」**という本質は共通です。Javaはインターフェースと抽象クラスによる厳密な分離を強制し、Pythonはよりシンプルで柔軟なオブジェクト合成を可能にします。

重要なのは、「継承ではなく合成を使う」という原則を理解し、変更に強い設計を心がけることです。

明日は構造パターンのCompositeパターンについて解説します。お楽しみに!

次回のテーマは、「Day 11 Compositeパターン:木構造を扱う」です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?