Skip to content

Instantly share code, notes, and snippets.

@KatagiriSo
Last active June 14, 2024 05:13
Show Gist options
  • Save KatagiriSo/f7c1bf769a22ec593560e851dd9e3725 to your computer and use it in GitHub Desktop.
Save KatagiriSo/f7c1bf769a22ec593560e851dd9e3725 to your computer and use it in GitHub Desktop.
Bridgeパターン

Bridgeパターン_katagiri.md

Bridge Pattern

参照: https://refactoring.guru/ja/design-patterns/bridge

概要

構造的パターンの一つ 巨大クラス、密接に関連するクラスの集まりを抽象と実装に分解し、それらを独立に変更できるようにする。

例題

幾何学クラスで形と色を持つクラスを作成する。

形: 正方形、円、三角形(それぞれに属性と描画メソッドを持つ) 色: 赤、緑、青(それぞれに属性を持つ)

クラスで形と色を持つクラスを作成すると 親クラスを作ったとして、そのサブクラスが9つ必要になる。 赤正方形、緑正方形、青正方形、赤円、緑円、青円、赤三角形、緑三角形、青三角形

Pythonで素直に実装すると以下のようになる。

class Shape:
    def __init__(self, color):
        self.color = color
    def draw(self):
        pass

class RedSquare(Shape):
    def __init__(self):
        super().__init__("Red")
    def draw(self):
        print("Draw Red Square")

Bridge Patternを用いる

形クラスと色クラスを分ける

class Color:
    def __init__(self, color):
        self.color = color

class Shape:
    def __init__(self, color):
        self.color = color
    def draw(self):
        pass

class Square(Shape):
    def draw(self):
        print(f"Draw {self.color.color} Square")

class Circle(Shape):
    def draw(self):
        print(f"Draw {self.color.color} Circle")

class Red(Color):
    def __init__(self):
        super().__init__("Red")

class Green(Color):
    def __init__(self):
        super().__init__("Green")

# use
red = Red()
green = Green()
square = Square(red)
circle = Circle(green)
square.draw()
circle.draw()

Shapeが抽象クラス, Colorが実装クラス Square, Circleが"具体的な抽象クラス" Red, Greenが"具体的な実装クラス"となる。

現実に近い例1

APIとGUI

抽象クラスとしてGUIと実装クラスとしてAPIを作成し、それぞれの具体クラスを作成する。

GUIは顧客向け、開発者向け、コンソール向けなど各実装がある。 APIはバージョン1, バージョン2など各実装がある(?)

現実に近い例2

各種デバイスがあり、それをデバイスを操作するリモコンがある場合。 ラジオ、テレビ、etc. 単純リモコン、多機能リモコン、etc.

Bridpeパターンを用いることで、デバイスとリモコンを独立に変更できるようになる。 抽象クラスとしてRemotoクラスと実装クラスとしてDeviceクラスに分ける。

Deviceクラスには抽象化された操作群があり、それを継承した具体的実装クラスがある。 RemotoクラスはDeviceクラスを持ち、それを継承した具体的抽象クラスがある。

class Device:
    def __init__(self):
        pass
    def is_enable(self):
        pass
    def enable(self):
        pass
    def disable(self):
        pass
    def get_volume(self):
        pass
    def set_volume(self, volume):
        pass
    def get_channel(self):
        pass
    def set_channel(self, channel):
        pass

class Remote:
    def __init__(self, device):
        self.device = device
    def toggle_power(self):
        if self.device.is_enable():
            self.device.disable()
        else:
            self.device.enable()
    def volume_down(self):
        self.device.set_volume(self.device.get_volume() - 10)
    def volume_up(self):
        self.device.set_volume(self.device.get_volume() + 10)
    def channel_down(self):
        self.device.set_channel(self.device.get_channel() - 1)
    def channel_up(self):
        self.device.set_channel(self.device.get_channel() + 1)

class TV(Device):
    def __init__(self):
        self.enable = False
        self.volume = 50
        self.channel = 1
    def is_enable(self):
        return self.enable
    def enable(self):
        self.enable = True
    def disable(self):
        self.enable = False
    def get_volume(self):
        return self.volume
    def set_volume(self, volume):
        self.volume = volume
    def get_channel(self):
        return self.channel
    def set_channel(self, channel):
        self.channel = channel

class SimpleRemote(Remote):
    pass

class AdvancedRemote(Remote):
    def mute(self):
        self.device.set_volume(0)

# use
tv = TV()
simple_remote = SimpleRemote(tv)
simple_remote.toggle_power()

advanced_remote = AdvancedRemote(tv)
advanced_remote.toggle_power()

# 適応性
1. ちょっとした違いの変種がある場合変種が増えれば増える程全体把握が難しくなるそのような場合にBridgeパターンを用いることで変種を独立に変更できるようになる2. クラスが幾つかの直交する次元での拡張が必要な場合Bridgeパターンを用いることでそれぞれの次元を独立に拡張できるようになる3. 実行時に実装を選択する必要がある場合Bridgeパターンを用いることで実装を独立に選択できるようになる: 出力先がファイルかプリンタか画面かetc.
( Strategy パターンとは違うらしい。。)

# 実装の手順
1. 直交する次元の特定
2. 抽象クラスの特定
3. クラスの操作を抽象化(絶対に使うもののみ)
4. 実装クラスの作成
5. 抽象クラス内では実装クラスを参照し操作は実装クラスに委譲する

## 長所と短所
長所
- クラスの変更が容易
短所
- 高度に密着しているクラスには適用しにくい(次元が分割できない)

## 他パターンとの関係
1. Adapterパターンとの違い Builderは設計当初から使われるがAdapterは既存のクラスに適用する
2. State, Startegyパターンとの違い State, Startegyは状態やアルゴリズムを独立に変更するがBridgeは実装を独立に変更する
3. Abstruct Factoryパターンとの関係 一緒に使うことが多い4. Builderパターンとの関係 一緒に使うことが多い
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment