추상(abstraction)과 구현(implementation)을 별도 계층으로 분리해 각각 독립적으로 확장하게 하는 패턴.
무엇을 푸는가 — 클래스 폭발
도형(원·사각형) × 렌더링(벡터·래스터) 을 상속으로 조합하면
VectorCircle · RasterCircle · VectorSquare · RasterSquare → 2×2 = 4개
- 도형 3종 × 렌더링 3종이면 — → 9개
두 축이 곱해지며 클래스가 폭발한다
// 구현 계층 — 독립적으로 확장
interface Renderer { void drawCircle(int r); }
class VectorRenderer implements Renderer { ... }
class RasterRenderer implements Renderer { ... }
// 추상 계층 — 구현을 '가지고' 있다 (상속이 아니라 합성)
abstract class Shape {
protected final Renderer renderer; // ← 이 참조가 '다리(bridge)'
Shape(Renderer r) { this.renderer = r; }
abstract void draw();
}
class Circle extends Shape {
void draw() { renderer.drawCircle(radius); }
}
// 2 + 2 = 4개로 끝. 3종씩이면 3 + 3 = 6개
new Circle(new VectorRenderer()).draw();
곱셈이 덧셈으로 바뀐다는 것이 핵심 이득이다.
Adapter와의 차이
-
Adapter — 이미 만들어진 것들을 나중에 맞춘다 (사후·비호환 해소)
-
Bridge — 처음부터 두 축이 따로 변할 것을 알고 나눈다 (사전·설계 의도)
-
같은 구조(합성)로 보이지만 시점과 의도가 다르다
실무에서
JDBC Driver 인터페이스가 구현 계층, DriverManager 가 추상 계층
SLF4J API(추상) ↔ Logback·Log4j(구현) — 대표적 Bridge
AWT Peer 아키텍처
면접 함정
- ❌ "Adapter와 같다" → 구조는 비슷해도 사후 호환 vs 사전 분리로 의도가 다르다.
- ❌ 축이 하나인데 도입 → 변하는 축이 둘 이상일 때만 값을 한다.
어떻게 알아보나 — 두 축을 찾는다
"무엇을" 과 "어떻게" 가 따로 변하면 Bridge 후보다
| 메시지 종류(알림·경고·긴급) | × | 전송 수단(이메일·SMS·푸시) | |
|---|---|---|---|
| 리포트 종류(월간·연간) | × | 출력 형식(PDF·Excel·HTML) |
한 축이 고정이면 Strategy 나 단순 상속으로 충분하다
상속으로는 왜 안 되나
class VectorCircle extends Circle { } // 컴파일 타임 고정 — 못 바꾼다
shape.setRenderer(new RasterRenderer()); // 합성이면 런타임 교체 가능
실무 감각 — SLF4J
애플리케이션 → SLF4J API(추상) ← Logback·Log4j2·JUL(구현)
코드를 한 줄도 안 고치고 의존성만 바꿔 로깅 구현을 교체한다
도입 시점
처음부터 Bridge로 시작하지 않아도 된다. 두 번째 축이 실제로 생겼을 때 리팩터링해도 늦지 않다. "언젠가 다른 렌더러가 필요할 것 같아서"로 미리 나누면 쓰이지 않는 간접층만 남는다(YAGNI).