
상황: 그래픽 프로그램에서 여러 종류의 도형(Shape)을 여러 종류의 드로잉 라이브러리(DP1, DP2)로 그려야 한다. 상속만으로 해결하려 하면 추상화(Abstraction)와 구현(Implementation)이 강하게 결합되어 클래스가 기하급수적으로 증가한다.
핵심 문제: M개의 도형 × N개의 드로잉 라이브러리 = M×N개의 클래스 필요
Bad Code Example:
// 상속만 사용한 접근법 - 조합의 폭발 (Combinatorial Explosion)
abstract class Shape { abstract void draw(); }
abstract class Rectangle extends Shape { /* drawLine 추상 메서드 */ }
abstract class Circle extends Shape { /* drawCircle 추상 메서드 */ }
// 2개 도형 × 2개 구현 = 4개 클래스 (새 도형/구현 추가 시 급증!)
class V1Rectangle extends Rectangle { /* DP1 사용 */ }
class V2Rectangle extends Rectangle { /* DP2 사용 */ }
class V1Circle extends Circle { /* DP1 사용 */ }
class V2Circle extends Circle { /* DP2 사용 */ }
// 만약 DP3이 추가되면? → V3Rectangle, V3Circle 추가 필요
// 만약 Triangle이 추가되면? → V1Triangle, V2Triangle, V3Triangle 추가 필요
접근: 추상화 계층(Abstraction Hierarchy)과 구현 계층(Implementation Hierarchy)을 분리하고, 객체 합성(Composition)으로 연결한다.
핵심 원리:
분석 과정:
| 분석 유형 | 대상 | 결과 |
|---|---|---|
| Commonality Analysis (공통성) | Shape, Drawing | 추상 클래스로 표현 |
| Variability Analysis (가변성) | Rectangle/Circle, V1/V2Drawing | 구체 클래스로 구현 |
Good Code Example:
// Abstraction(추상화)이 Implementor(구현자)를 참조로 가짐
abstract class Shape {
private Drawing _dp; // Bridge: 구현 객체에 대한 참조
Shape(Drawing dp) { _dp = dp; }
abstract public void draw();
// 구현 객체에 작업을 위임(Delegation)
protected void drawLine(double x1, double y1, double x2, double y2) {
_dp.drawLine(x1, y1, x2, y2);
}
protected void drawCircle(double x, double y, double r) {
_dp.drawCircle(x, y, r);
}
}
// Client 코드: 런타임에 추상화와 구현을 독립적으로 조합
Drawing dp = new V1Drawing();
Shape rect = new Rectangle(dp, 1, 1, 2, 2);
rect.draw(); // 어떤 Drawing을 사용하는지 Shape은 알 필요 없음
장점:
단점:
Bridge Pattern (구조 패턴 - Structural Pattern)
1. Abstraction (추상화)
abstract class Shape {
private Drawing _dp; // Implementor 참조 (Bridge)
Shape(Drawing dp) { _dp = dp; }
abstract public void draw(); // 클라이언트가 호출할 인터페이스
// Implementor에 작업 위임
protected void drawLine(double x1, double y1, double x2, double y2) {
_dp.drawLine(x1, y1, x2, y2);
}
}2. RefinedAbstraction (정제된 추상화)
역할: Abstraction의 인터페이스를 확장하여 구체적인 기능 구현
Java Code:
class Rectangle extends Shape {
private double _x1, _y1, _x2, _y2;
public Rectangle(Drawing dp, double x1, double y1, double x2, double y2) {
super(dp); // Implementor 전달
_x1 = x1; _y1 = y1; _x2 = x2; _y2 = y2;
}
public void draw() {
// 부모 클래스의 위임 메서드 사용 (실제 구현은 모름)
drawLine(_x1, _y1, _x2, _y1);
drawLine(_x2, _y1, _x2, _y2);
drawLine(_x2, _y2, _x1, _y2);
drawLine(_x1, _y2, _x1, _y1);
}
}
class Circle extends Shape {
private double _x, _y, _r;
public Circle(Drawing dp, double x, double y, double r) {
super(dp);
_x = x; _y = y; _r = r;
}
public void draw() {
drawCircle(_x, _y, _r);
}
}
특징: 어떤 Drawing 구현을 사용하는지 전혀 알지 못함
3. Implementor (구현자 인터페이스)
abstract class Drawing {
abstract public void drawLine(double x1, double y1, double x2, double y2);
abstract public void drawCircle(double x, double y, double r);
}4. ConcreteImplementor (구체적 구현자)
역할: Implementor 인터페이스를 실제로 구현 (플랫폼별 구현)
Java Code:
class V1Drawing extends Drawing {
public void drawLine(double x1, double y1, double x2, double y2) {
DP1.draw_a_line(x1, y1, x2, y2); // DP1 라이브러리 사용
}
public void drawCircle(double x, double y, double r) {
DP1.draw_a_circle(x, y, r);
}
}
class V2Drawing extends Drawing {
public void drawLine(double x1, double y1, double x2, double y2) {
DP2.drawline(x1, x2, y1, y2); // DP2는 파라미터 순서가 다름!
}
public void drawCircle(double x, double y, double r) {
DP2.drawcircle(x, y, r);
}
}
특징: 각 플랫폼/라이브러리의 API 차이를 내부에서 처리
5. Client (클라이언트)
public class Client {
public static void main(String[] args) {
Drawing dp1 = new V1Drawing();
Drawing dp2 = new V2Drawing();
Shape r1 = new Rectangle(dp1, 1, 1, 2, 2); // DP1으로 그리는 사각형
Shape c1 = new Circle(dp2, 2, 2, 3); // DP2로 그리는 원
r1.draw();
c1.draw();
}
}| 비교 항목 | Bridge Pattern | Adapter Pattern |
|---|---|---|
| 목적 (Intent) | 추상화와 구현을 분리하여 독립적 확장 가능하게 함 | 호환되지 않는 인터페이스를 변환하여 함께 동작하게 함 |
| 적용 시점 | 설계 초기(up-front)에 적용하여 확장성 확보 | 설계 후(after) 기존 시스템에 적용 (리엔지니어링) |
| 구조적 차이 | 복잡한 엔티티의 추상화/구현 계층을 분리 | 단일 인터페이스만 변환 |
| 관계 | 추상화가 구현을 참조로 가짐 (Has-a) | 어댑터가 어댑티를 감싸거나 상속 |
// Bridge: 설계 시 미리 분리
Shape shape = new Rectangle(new V1Drawing(), ...); // 추상화 + 구현 조합
// Adapter: 기존 시스템 통합
OldSystem oldSys = new OldSystem();
NewInterface adapter = new OldSystemAdapter(oldSys); // 인터페이스 변환
| 비교 항목 | Bridge Pattern | Strategy Pattern |
|---|---|---|
| 분류 | 구조 패턴 (Structural) | 행동 패턴 (Behavioral) |
| 목적 | 추상화와 구현의 영구적 분리 | 알고리즘의 런타임 교체 |
| 계층 구조 | 추상화 쪽도 계층을 가질 수 있음 | Context는 보통 단일 클래스 |
| 의미론 | "구현 방식"의 분리 | "행동/전략"의 캡슐화 |
| 비교 항목 | 상속만 사용 | Bridge 패턴 사용 |
|---|---|---|
| 클래스 수 | M × N (기하급수적) | M + N (선형) |
| 결합도 | 추상화-구현 강결합 | 추상화-구현 약결합 |
| 확장성 | 새 차원 추가 시 모든 조합 필요 | 독립적으로 확장 가능 |
| 바인딩 시점 | 컴파일 타임 | 런타임 |
문제 1. Bridge 패턴은 객체의 생성을 담당하는 '생성 패턴(Creational Pattern)'에 속한다.
정답: X
해설: Bridge 패턴은 구조 패턴(Structural Pattern)이다. 클래스와 객체의 구성(composition)을 다룬다.
문제 2. Bridge 패턴에서 Abstraction은 Implementor를 상속받아 구현한다.
정답: X
해설: Abstraction은 Implementor를 멤버 변수로 참조(Has-a)하며, 상속이 아닌 합성(Composition)과 위임(Delegation)을 사용한다.
문제 3. Bridge 패턴을 사용하면 추상화 계층과 구현 계층을 런타임에 독립적으로 확장할 수 있다.
정답: O
해설: Bridge 패턴의 핵심 목적은 추상화와 구현을 분리하여 독립적으로 확장할 수 있게 하는 것이다.
문제. 다음 설명에 해당하는 디자인 패턴의 이름은?
"추상화(Abstraction)를 구현(Implementation)으로부터 분리하여 둘이 독립적으로 변할 수 있게 한다."
정답: Bridge Pattern
해설: Bridge 패턴의 GoF 정의이다. 추상화 계층과 구현 계층을 별도의 클래스 계층으로 분리하고, 합성을 통해 연결한다.
[상황] 메시지 발송 시스템을 설계하려 한다. 메시지의 종류로는 TextMessage, EmailMessage가 있고, 발송 플랫폼으로는 SMSSender, PushSender가 있다. 새로운 메시지 종류나 발송 플랫폼이 추가될 예정이다.
3-1. 패턴 선택: 이 상황에 가장 적합한 디자인 패턴은?
정답: Bridge Pattern
해설: 메시지 종류(추상화)와 발송 플랫폼(구현)이라는 두 개의 독립적인 변화 축이 존재하므로 Bridge 패턴이 적합하다. 상속만 사용하면 2×2=4개, 3×3=9개로 클래스가 급증한다.
3-2. 구조 설계: 클래스 구조를 텍스트로 설명하시오.
정답:
- Abstraction:
Message(추상 클래스) -MessageSender를 멤버로 가짐- RefinedAbstraction:
TextMessage,EmailMessage-Message를 상속- Implementor:
MessageSender(인터페이스/추상 클래스) -send(String content)메서드 정의- ConcreteImplementor:
SMSSender,PushSender-MessageSender구현
3-3. 구현: TextMessage 클래스의 send() 메서드를 구현하시오. (부모 클래스에 MessageSender sender 멤버가 있다고 가정)
정답:
class TextMessage extends Message { private String text; public TextMessage(MessageSender sender, String text) { super(sender); this.text = text; } public void send() { // 구현 객체에 위임 sender.send("[TEXT] " + text); } }
3-4. Client 코드: SMS로 텍스트 메시지를 발송하는 객체를 생성하는 한 줄짜리 Java 코드를 작성하시오.
정답:
Message msg = new TextMessage(new SMSSender(), "Hello Bridge!");
문제. Bridge 패턴에서 "Abstraction"과 프로그래밍 언어의 "abstract class"는 같은 개념인가? 그렇지 않다면 Bridge 패턴에서 말하는 Abstraction의 의미를 설명하시오.
정답: 같은 개념이 아니다.
해설:
- 프로그래밍 언어의 abstract class: 인스턴스화할 수 없고 서브클래스에서 구현해야 하는 추상 메서드를 가진 클래스
- Bridge 패턴의 Abstraction: 고수준 제어 계층(high-level control layer)을 의미한다. 실제 작업을 직접 수행하지 않고, 구현 계층(Implementation/Platform)에 작업을 위임한다.
예를 들어, Shape(Abstraction)는 "사각형을 그린다"라는 고수준 개념을 표현하고, Drawing(Implementor)은 "선을 긋는다"라는 저수준 primitive 작업을 제공한다. 이 둘은 독립적으로 확장 가능하며, 반드시 Java의 abstract class로 구현될 필요는 없다 (interface로도 가능).
┌─────────────────────┐ ┌─────────────────────┐
│ Abstraction │ │ Implementor │
│ (Shape) │◇───────▶│ (Drawing) │
│ +draw() │ has-a │ +drawLine() │
│ #drawLine() │ │ +drawCircle() │
└─────────┬───────────┘ └─────────┬───────────┘
│ │
│ extends │ extends
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ RefinedAbstraction │ │ ConcreteImplementor │
│ (Rectangle, Circle) │ │ (V1Drawing,V2Drawing│
└─────────────────────┘ └─────────────────────┘
핵심: 추상화와 구현을 분리 → 클래스 수 M×N → M+N