Mobile & App Stack/Java Software Architecture & Patterns

브리지 패턴(Bridge Pattern) 완벽 정리: 클래스 폭발을 막는 설계의 기술

임베디드 친구 2024. 12. 28. 10:30
반응형

소프트웨어의 기능을 확장하다 보면 클래스가 걷잡을 수 없이 많아지는 경험을 하게 됩니다. 예를 들어 '모양' 클래스를 '색상'별로 확장하다 보면 RedCircle, BlueCircle, RedSquare 등 조합에 따라 클래스가 기하급수적으로 늘어나죠. 이를 '클래스 폭발'이라고 합니다.

오늘은 이러한 문제를 해결하고 추상화와 구현을 분리하여 독립적인 확장을 가능케 하는 브리지 패턴(Bridge Pattern)에 대해 알아보겠습니다.

Generated by Gemini AI.


1. 브리지 패턴이란?

브리지 패턴은 추상화(Abstraction)와 구현(Implementation)을 별도의 클래스 계층으로 분리하여, 양쪽이 서로의 코드 수정 없이 독립적으로 확장될 수 있도록 연결하는 패턴입니다.

왜 브리지 패턴인가? (상속 vs 합성)

  • 상속(Inheritance): 기능을 확장할 때 부모-자식 간의 강한 결합이 생겨 유연성이 떨어집니다.
  • 브리지(Composition): "Has-a" 관계를 통해 구현체를 참조하므로, 새로운 기기나 새로운 리모컨 기능이 추가되어도 서로 영향을 주지 않습니다.

2. 브리지 패턴의 구조

이 패턴은 두 개의 독립적인 계층 구조를 다리로 잇는 듯한 형태를 띱니다.

  1. Abstraction (추상화): 상위 수준의 제어 로직을 담고 있으며, Implementor를 참조합니다.
  2. RefinedAbstraction (개선된 추상화): 추상화된 기능을 확장합니다. (예: 일반 리모컨에 '음소거' 기능 추가)
  3. Implementor (구현자): 구체적인 기능을 수행하기 위한 공통 인터페이스입니다.
  4. ConcreteImplementor (구체적인 구현): 각 기기(TV, 라디오 등)에 맞는 실제 로직을 구현합니다.

3. Java 구현 예제: 기기와 리모컨의 독립적 확장

Step 1. Implementor 계층 (기기 로직)

Java
 
// 구현체 인터페이스
interface Device {
    void turnOn();
    void turnOff();
    void setVolume(int volume);
}

// TV 구현
class TV implements Device {
    public void turnOn() { System.out.println("📺 TV를 켭니다."); }
    public void turnOff() { System.out.println("📺 TV를 끕니다."); }
    public void setVolume(int v) { System.out.println("📺 TV 볼륨을 " + v + "로 조절합니다."); }
}

// 라디오 구현
class Radio implements Device {
    public void turnOn() { System.out.println("📻 라디오를 켭니다."); }
    public void turnOff() { System.out.println("📻 라디오를 끕니다."); }
    public void setVolume(int v) { System.out.println("📻 라디오 볼륨을 " + v + "로 조절합니다."); }
}

Step 2. Abstraction 계층 (리모컨 제어)

Java
 
// 추상화 클래스: 기기를 제어하는 '다리' 역할
abstract class RemoteControl {
    protected Device device; // 상속 대신 구성을 사용 (Bridge)

    public RemoteControl(Device device) {
        this.device = device;
    }

    public void power() {
        device.turnOn();
    }
}

// 확장된 추상화: 고급 리모컨 기능 추가
class AdvancedRemote extends RemoteControl {
    public AdvancedRemote(Device device) { super(device); }

    public void mute() {
        System.out.println("🔇 음소거 버튼을 눌렀습니다.");
        device.setVolume(0);
    }
}

Step 3. 클라이언트 테스트

Java
 
public class BridgeClient {
    public static void main(String[] args) {
        // TV를 고급 리모컨으로 제어
        AdvancedRemote tvRemote = new AdvancedRemote(new TV());
        tvRemote.power();
        tvRemote.mute();

        System.out.println("-----------------");

        // 라디오를 같은 리모컨으로 제어 (구현체만 교체)
        AdvancedRemote radioRemote = new AdvancedRemote(new Radio());
        radioRemote.power();
        radioRemote.mute();
    }
}

4. 브리지 패턴의 장단점

👍 장점

  • 독립적인 확장: 인터페이스와 구현을 완전히 분리하여 각각의 계층을 자유롭게 확장할 수 있습니다.
  • 상세 구현 은닉: 클라이언트는 추상화 계층만 바라보므로 내부 구현의 변경으로부터 자유롭습니다.
  • 클래스 폭발 방지: $M$개의 모양과 $N$개의 색상이 있을 때 상속은 $M \times N$개의 클래스가 필요하지만, 브리지는 $M+N$개면 충분합니다.

👎 단점

  • 복잡성 증가: 간단한 시스템에서는 추상화 계층이 추가되는 것이 오히려 코드를 복잡하게 만들 수 있습니다.
  • 추상화 비용: 처음 설계 단계에서 계층을 나누는 깊은 통찰이 필요합니다.

5. 실생활 비유와 사례

  • 그래픽 드라이버: OS의 UI 인터페이스(추상화)는 동일하지만, 실제 그래픽 카드(NVIDIA, AMD)의 드라이버(구현)는 각기 다르게 작동합니다.
  • 멀티 플랫폼 앱: 코드의 비즈니스 로직은 하나지만, Android용 UI와 iOS용 UI 구현을 다르게 가져가는 경우에 적합합니다.

결론

브리지 패턴은 "어떤 변경이 다른 계층에 파급되지 않도록 벽을 세우는 작업"입니다. 시스템이 복잡해지고 여러 차원의 확장이 예상된다면, 상속의 늪에 빠지기 전에 브리지 패턴을 도입해 보세요!


도움이 되셨다면 공감과 구독 부탁드립니다!

객체 지향 설계에 대한 더 깊은 이야기는 댓글로 함께 나눠요.

반응형