Post

브릿지(Bridge) 패턴

클래스 기반 브릿지 패턴 이해

브릿지(Bridge) 패턴

브릿지 패턴은 추상화(Abstraction)와 구현(Implementation)을 분리하여 각각 독립적으로 확장할 수 있도록 하는 구조적(Structural) 디자인 패턴이다.

즉, 기능 계층구현 계층을 분리하여 유지보수성과 확장성을 높이는 것이 목적이다.

주요 개념

  • 추상화(Abstraction)
    • 기능을 정의하는 상위 계층 (인터페이스 역할)
  • 구현(Implementation)
    • 실제 동작을 담당하는 하위 계층
  • Bridge
    • 추상화 계층이 직접 구현을 참조하는 것이 아닌, 구현 계층을 포인터나 참조(Composition)로 연결 → 느슨한 결합(Loose Coupling)을 유지
classDiagram
    class Abstraction {
        - Implementation* impl
        + operation()
    }
    
    class RefinedAbstraction {
        + operation()
    }
    
    class Implementation {
        <<interface>>
        + operationImpl()
    }
    
    class ConcreteImplementationA {
        + operationImpl()
    }
    
    class ConcreteImplementationB {
        + operationImpl()
    }

    Abstraction --> Implementation : "브릿지 (Bridge)"
    RefinedAbstraction --|> Abstraction : "추상화 확장"
    Implementation <|.. ConcreteImplementationA
    Implementation <|.. ConcreteImplementationB

장점

  • 기능과 구현을 독립적으로 확장 가능
    • 기능을 추가하면서도 구현을 수정할 필요가 없음
  • 코드 유지보수 용이
    • 변경 사항이 생겨도 최소한의 수정으로 해결 가능
  • 다형성을 활용하여 동적으로 구현 변경 가능
    • 런타임에 다른 구현을 교체하는 것이 가능

주의점

  • 추상화와 구현의 관계 명확화
    • 브릿지 패턴의 핵심은 추상화구현의 분리
    • 너무 세분화하면 코드가 복잡해지고, 너무 단순하면 브릿지 패턴을 쓰는 의미가 줄어들음
  • 객체 간 결합도(Coupling)를 적절히 조정
    • 브릿지 패턴은 Composition(구성)을 사용하여 구현을 위임하는 방식이라 동적으로 객체를 교체가능
    • 하지만, 너무 많은 계층을 도입하면 관리가 어려워짐 → 필요한 만큼만 분리 필요
  • 적절한 인터페이스 설계
    • 구현 인터페이스(Implementation)가 너무 일반적이면 유연성은 높지만 불필요한 메서드 발생 가능성 있음
    • 반대로 너무 특정 기능에 맞춰 설계하면 재사용성이 떨어짐
  • 객체의 생명주기, 메모리 관리
    • 추상화구현을 포인터로 관리 필요
      • 스마트 포인터 (std::shared_ptr) 사용 → 객체가 안전하게 공유되도록 관리
    • 소멸자에서 적절히 해제 → 메모리 누수 방지
  • 너무 작은 프로젝트에서는 오버헤드가 발생할 수 있음
    • 단순한 기능이라면 굳이 브릿지 패턴을 사용할 필요 없음.
    • 브릿지 패턴을 사용하면 코드 구조가 복잡해지므로, 확장 가능성이 클 때만 적용하는 것을 추천

예시

C++을 이용하여 예시 코드를 작성하였다. 아래 링크에서 전체를 확인할 수 있다.

https://github.com/grade-e/bridge-cpp-container

  • 장치(Device): TV, 라디오 같은 전자기기 (구현부)
  • 리모컨(RemoteControl): 전자기기를 조작하는 컨트롤러 (추상화 계층)
  • Bridge 역할: 리모컨은 특정 장치의 구현을 직접 사용하지 않고, 인터페이스를 통해 연결

Class diagram

classDiagram
    class Device {
        +void turnOn()
        +void turnOff()
        +void setVolume(int volume)
        +int getVolume()
    }

    class TV {
        +void turnOn()
        +void turnOff()
        +void setVolume(int volume)
        +int getVolume()
    }

    class Radio {
        +void turnOn()
        +void turnOff()
        +void setVolume(int volume)
        +int getVolume()
    }

    class RemoteControl {
        - Device* device
        +void SetDevice()
        +void turnOn()
        +void turnOff()
        +void volumeUp()
        +void volumeDown()
    }

    class AdvancedRemoteControl {
        +void mute()
    }

    Device <|-- TV
    Device <|-- Radio
    RemoteControl *-- Device
    AdvancedRemoteControl --|> RemoteControl

코드

Device()

  • 구현의 인터페이스 역할
  • 구체적인 장치들이 공통적으로 가져야 할 기본 동작을 정의
  • 추상 클래스이므로, 직접 객체로 생성되지 않음
1
2
3
4
5
6
7
8
class Device {
   public:
    virtual void TurnOn() = 0;
    virtual void TurnOff() = 0;
    virtual void SetVolume(int volume) = 0;
    virtual int GetVolume() = 0;
    virtual ~Device() = default;
};

TV, Radio

  • TV와 Radio는 Device 인터페이스를 상속받아 구체적인 동작을 구현
  • 새로운 장치를 추가할 경우 Device 인터페이스를 구현하기만 하면 됨
    • 리모컨(RemoteControl) 코드 수정 없이 새로운 장치 추가가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Radio : public Device {
   private:
    int volume = 5;

   public:
    void TurnOn() override { std::cout << "Radio on" << std::endl; }

    void TurnOff() override { std::cout << "Radio off" << std::endl; }

    void SetVolume(int volume) override {
        this->volume = volume;
        std::cout << "Radio volume: " << volume << std::endl;
    }

    int GetVolume() override { return volume; }
};

class TV : public Device {
   private:
    int volume = 10;

   public:
    void TurnOn() override { std::cout << "TV on" << std::endl; }

    void TurnOff() override { std::cout << "TV off" << std::endl; }

    void SetVolume(int volume) override {
        this->volume = volume;
        std::cout << "TV volume: " << volume << std::endl;
    }

    int GetVolume() override { return volume; }
};

RemoteControl

  • RemoteControl은 추상화 계층을 담당
  • 직접 TV나 Radio를 포함하지 않고, Device 인터페이스를 통해 동작을 위임함.
    • RemoteControl이 Device의 동작을 직접 수행하지 않고 “연결” 만 담당
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class RemoteControl {
   protected:
    std::shared_ptr<Device> device;  // 장치와 연결 (Bridge 역할)
   public:
    explicit RemoteControl(std::shared_ptr<Device> dev)
        : device(std::move(dev)) {}

    void SetDevice(std::shared_ptr<Device> newDevice) {
        device = std::move(newDevice); // 새로운 장치로 변경
    }

    virtual void TurnOn() { device->TurnOn(); }

    virtual void TurnOff() { device->TurnOff(); }

    virtual void VolumeUp() {
        int vol = device->GetVolume();
        device->SetVolume(vol + 1);
    }

    virtual void VolumeDown() {
        int vol = device->GetVolume();
        device->SetVolume(vol - 1);
    }
}

AdvancedRemoteControl

  • AdvancedRemoteControl은 RemoteControl을 확장한 추상화의 확장(Refined Abstraction)
  • mute() 기능을 추가했지만, 기존 Device 인터페이스는 수정할 필요 없음
    • 추상화 계층을 독립적으로 확장가능
1
2
3
4
5
6
7
8
9
10
class AdvancedRemoteControl : public RemoteControl {
   public:
    explicit AdvancedRemoteControl(std::shared_ptr<Device> dev)
        : RemoteControl(std::move(dev)) {}

    void Mute() {
        std::cout << "command: mute" << std::endl;
        device->SetVolume(0);
    }
};

main()

  • RemoteControl이 Device 객체(TV, Radio)를 참조하여 동작 수행
    • RemoteControl이 Device와 직접적으로 연결되는 것이 아니라 인터페이스를 통해 간접적으로 연결
  • RemoteControl이 Device 인터페이스를 직접 포함(Composition)하면서도, 특정 구현(TV, Radio)에 의존하지 않음
    • RemoteControl을 수정하지 않고도 Device를 확장 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main() {
    std::shared_ptr<Device> tv = std::make_shared<TV>();
    std::shared_ptr<Device> radio = std::make_shared<Radio>();

    RemoteControl remote(tv);  // connect TV ↔ basicRemote
    remote.TurnOn();
    remote.TurnOff();

    std::cout << "--------Change device--------" << std::endl;

    remote.SetDevice(radio);
    remote.TurnOn();
    remote.TurnOff();

    std::cout << "-------Advanced remote-------" << std::endl;

    AdvancedRemoteControl Remote_mk2(radio);  // connect Radio ↔ advancedRemote
    Remote_mk2.TurnOn();
    Remote_mk2.Mute();
    Remote_mk2.TurnOff();

    return 0;
}

실행 결과

1
2
3
4
5
6
7
8
9
10
TV on
TV off
--------Change device--------
Radio on
Radio off
-------Advanced remote-------
Radio on
command: mute
Radio volume: 0
Radio off
This post is licensed under CC BY 4.0 by the author.