프로그래밍/디자인패턴

브리지 패턴(Bridge Pattern) 완벽 정리

Jinwookoh 2025. 12. 7. 18:27

"모양(Shape)에는 원과 사각형이 있습니다. 색상(Color)에는 빨강과 파랑이 있습니다."

이 간단한 요구사항을 구현하기 위해 RedCircle, BlueCircle, RedSquare, BlueSquare 클래스를 만드는 순간 지옥문이 열립니다. 만약 Triangle 모양이 추가되고 Green 색상이 추가된다면? 클래스는 $3 \times 3 = 9$개가 됩니다. $M \times N$으로 클래스가 기하급수적으로 늘어나는 현상, 이것이 **클래스 폭발(Class Explosion)**입니다.

이 문제를 해결하기 위해 **"모양"과 "색상"을 분리하고 다리로 연결(Bridge)**하는 것이 브리지 패턴의 핵심입니다.

Shutterstock
 

1. 문제 상황: 상속을 통한 확장 (Inheritance) - "M × N의 저주"

기능(추상화)과 구현을 상속 하나로 해결하려고 할 때 발생하는 문제입니다.

코드 예시 (나쁜 예)

Java
 
// 모양 정의
abstract class Shape {}

// 색상별 모양 정의 (상속의 남용)
class RedCircle extends Shape { ... }
class BlueCircle extends Shape { ... }
class RedSquare extends Shape { ... }
class BlueSquare extends Shape { ... }

// 만약 초록색(Green)이 추가된다면?
// GreenCircle, GreenSquare 클래스를 또 만들어야 함.

변화하는 축이 두 개(모양, 색상) 이상이면 상속 구조는 감당할 수 없을 만큼 비대해집니다.


2. 해결책: 브리지 패턴 (Composition) - "M + N의 기적"

상속을 사용하는 대신, 두 개의 독립적인 계층(추상화구현)으로 나누고, 이 둘을 **합성(Composition)**으로 연결합니다.

구조

  • Abstraction (추상화): 기능 계층의 최상위 클래스 (Shape). 내부에 Implementor를 가집니다.
  • Implementor (구현자): 구현 계층의 인터페이스 (Color).
  • ConcreteImplementor: 실제 구현체 (Red, Blue).

코드 예시

Java
 
// 1. Implementor (구현 계층: 색상)
interface Color {
    String fill();
}

class Red implements Color {
    public String fill() { return "빨간색"; }
}

class Blue implements Color {
    public String fill() { return "파란색"; }
}

// 2. Abstraction (기능 계층: 모양)
abstract class Shape {
    protected Color color; // 이것이 바로 '다리(Bridge)' 역할!

    // 생성자를 통해 구현 객체(Color)를 주입받음
    public Shape(Color color) {
        this.color = color;
    }

    abstract void draw();
}

// 3. Refined Abstraction (구체적 기능)
class Circle extends Shape {
    public Circle(Color color) { super(color); }

    @Override
    void draw() {
        System.out.println(color.fill() + " 원을 그립니다.");
    }
}

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

    @Override
    void draw() {
        System.out.println(color.fill() + " 사각형을 그립니다.");
    }
}

사용 (Client)

Java
 
// 빨간색 원
Shape redCircle = new Circle(new Red());
redCircle.draw();

// 파란색 사각형
Shape blueSquare = new Square(new Blue());
blueSquare.draw();

이제 Triangle을 추가하려면 Shape만 상속받으면 되고, Green을 추가하려면 Color만 구현하면 됩니다. 클래스 개수는 $M \times N$이 아니라 $M + N$이 됩니다.


3. 실무 예시: JDBC 드라이버

자바 개발자가 매일 쓰는 **JDBC(Java Database Connectivity)**가 브리지 패턴의 교과서적인 예시입니다.

  • Abstraction (기능): java.sql.Connection, java.sql.DriverManager (자바가 제공)
    • 개발자는 Connection.createStatement() 같은 표준 메서드만 씁니다.
  • Implementor (구현): com.mysql.jdbc.Driver, oracle.jdbc.driver.OracleDriver (각 벤더사가 제공)
    • 실제 DB 통신 로직은 벤더사마다 다릅니다.

개발자는 오라클을 쓰든 MySQL을 쓰든 코드를 바꾸지 않습니다. DriverManager라는 다리(Bridge)가 실제 구현체(Driver)를 연결해 주기 때문입니다.


요약: 어댑터 패턴과 무엇이 다른가?

둘 다 "인터페이스를 연결한다"는 점은 비슷해서 자주 혼동됩니다.

구분 Adapter Pattern Bridge Pattern
목적 호환성 (Compatibility) 독립성 (Independence)
적용 시점 코드가 다 짜인 후 (After) 설계 단계부터 (Before)
해결 문제 "인터페이스가 안 맞아서 연결이 안 돼요." "상속이 너무 복잡해서 확장이 힘들어요."
구조 기존 객체를 감싸서 변환 추상화와 구현을 분리하여 연결

결론

"이미 존재하는 클래스를 재사용하고 싶은데 인터페이스가 안 맞다"면 어댑터 패턴을 쓰세요.

하지만 "OS(Windows, Mac) 종류와 UI(버튼, 창) 종류가 계속 늘어날 것 같다" 처럼, 여러 방향으로 확장이 예상되는 시스템을 설계해야 한다면 반드시 브리지 패턴을 사용하여 상속의 저주를 피해야 합니다.