"모양(Shape)에는 원과 사각형이 있습니다. 색상(Color)에는 빨강과 파랑이 있습니다."
이 간단한 요구사항을 구현하기 위해 RedCircle, BlueCircle, RedSquare, BlueSquare 클래스를 만드는 순간 지옥문이 열립니다. 만약 Triangle 모양이 추가되고 Green 색상이 추가된다면? 클래스는 $3 \times 3 = 9$개가 됩니다. $M \times N$으로 클래스가 기하급수적으로 늘어나는 현상, 이것이 **클래스 폭발(Class Explosion)**입니다.
이 문제를 해결하기 위해 **"모양"과 "색상"을 분리하고 다리로 연결(Bridge)**하는 것이 브리지 패턴의 핵심입니다.

1. 문제 상황: 상속을 통한 확장 (Inheritance) - "M × N의 저주"
기능(추상화)과 구현을 상속 하나로 해결하려고 할 때 발생하는 문제입니다.
코드 예시 (나쁜 예)
// 모양 정의
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).
코드 예시
// 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)
// 빨간색 원
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(버튼, 창) 종류가 계속 늘어날 것 같다" 처럼, 여러 방향으로 확장이 예상되는 시스템을 설계해야 한다면 반드시 브리지 패턴을 사용하여 상속의 저주를 피해야 합니다.
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 플라이웨이트 패턴(Flyweight Pattern) 완벽 정리 (0) | 2025.12.07 |
|---|---|
| 방문자 패턴(Visitor Pattern) 완벽 정리 (0) | 2025.12.07 |
| 중재자 패턴(Mediator Pattern) 완벽 정리 (0) | 2025.12.07 |
| 퍼사드 패턴(Facade Pattern) 완벽 정리 (1) | 2025.12.07 |
| 데코레이터 패턴(Decorator Pattern) 총정리 (0) | 2025.12.07 |