[ Programming > Design Pattern ]
[디자인 패턴] 브릿지 패턴
브릿지 패턴(Bridge Pattern)
브릿지 패턴은 추상화 계층(Abstraction)과 구현 계층(Implementor)를 분리해서 독립적으로 확장할 수 있게 만드는 구조 패턴이에요.
추상화 계층을 기능 클래스 계층이라고 보기도 해요. 그래서 기능을 추가하고 싶으면 추상화 계층을, 구현을 추가하고 싶으면 구현 계층을 활용하면 되니 독립적으로 확장이 가능하게 되는 거에요.
브릿지 패턴 구조

Abstraction(추상화 계층, 기능 계층)과 Implementor(구현 계층)을 분리하여 이어주는 구조가 되기 때문에 브릿지 패턴이에요. 두 계층을 직접 묶지 않고, 중간에 다리를 놓아 서로 독립적으로 발전할 수 있도록 이은 거에요. 그림의 각 요소에 대해 설명해 볼게요.
Abstraction (추상화 계층의 상위 역할, 기능 계층의 상위 역할)
역할: Client가 사용하는 추상 인터페이스로 내부에 Implementor를 참조하여 구현 일부를 위임해요.
특징: 어떤 Implementor를 사용할 지 모르지만, 단지 호출하여 기능을 만드는데 사용해요.
예시: 리모컨 -> TV가 삼성인지 LG인지 모르지만 "전원 키기", "전원 끄기"가 가능해요.
RefinedAbstraction (추상화 계층의 구현 클래스)
역할: Abstraction에 정의된 operation()을 실제로 구현 및 확장해요. 여전히 Implementor를 사용해여 구현해요.
특징: Abstraction을 상속받기 때문에 기능 계층과 약한 결합을 가져요.
예시: LG 리모콘, 종합 리모콘 등..
Implementor (구현 계층의 인터페이스)
역할: 실제 기능을 담당하는 구현 객체들이 따라야 할 인터페이스로 실제 구현은 ConcreteImplementor에서 해요.
특징: RefinedAbstraction 기능을 위한 api 제공 및 구현 로직의 최소 기준을 정의해요.
예시: TV 본체로 TV의 전원 켜기, 끄기 등을 할 수 있어요
ConcreteImplementor (구현 계층의 구현체)
역할: Implementor를 구체적으로 구현하는 클래스들이에요
특징: Abstraction을 몰라도 Implementor만 알고 상속받아 구현하면 되요.
예시: LG TV, 삼성 TV
이제 짧게 테이블로 요약해 볼게요
요소 | 역할 | 설명 |
|---|---|---|
Abstraction | 추상화 계층 | 클라이언트가 사용하는 상위 인터페이스, 구현체에 위임 |
RefinedAbstraction | 추상화 확장 | Abstraction을 확장한 실제 기능 클래스 |
Implementor | 구현 계층의 인터페이스 | 실제 기능을 담당하는 구현체 인터페이스 |
ConcreteImplementorA/B | 구현체 | Implementor를 실제로 구현하는 클래스들 |
그리고 마지막으로 브릿지 패턴에 대해 최대한 짧게 정리해 볼게요.
정리
기능과 구현을 분리한 패턴으로 Abstraction(기능 계층)은 Implementor(구현)을 구성으로 갖고 기능을만드는데 구현을 사용한다.
기능을 추가하고 싶으면 Abstraction을 상속받는 객체를 추가, 구현은 Implementor를 상속받는 객체를 추가하면 된다.
기능은 구현 추상클래스만 알면 되고, 구현체는 기능을 몰라도 된다.
LG TV를 구현체로 봤지만, 사실 LG TV도 Display라는 기능이 있을 때, 이 Display를 위한 구현을 Panel에게 위임할 수 있다. 그래서 LG Panel, BOE 패널, 삼성 패널을 사용하여 LG TV를 만들 수 있을 것이다. 그래서 LG TV도 다른 관점에서 언제든 추상화 계층이 될 수 있다.
예시 - 알림 시스템
회사 알림 시스템을 만든다고 해봐요. 그런데 Email로 알림, Slack으로 알림, SMS로 알림 등 알림을 보내야할 곳이 점점 많아져요. 그리고 알림도 긴급 알림, 일반 알림 등 알림의 종류도 다양해져요. 이 상황을 브릿지 패턴을 적용해 확장성과 책임 분리를 확보해봐요.
Implementor - 전송 채널
interface NotificationChannel { sendMessage(message: string): void;}ConcreteImplementor - 이메일 / 슬랙 채널
class EmailSender implements NotificationChannel { sendMessage(message: string) { console.log(`이메일 메시지 : ${message}`); }}class SlackSender implements NotificationChannel { sendMessage(message: string) { console.log(`슬랙 메시지: ${message}`); }}Abstraction - 알림 타입
abstract class Notification { protected sender: NotificationChannel ; constructor(sender: NotificationChannel ) { this.sender = sender; } abstract notify(message: string): void;}RefinedAbstraction - 긴급 알림, 일반 알림
class NormalNotification extends Notification { notify(message: string) { this.sender.sendMessage(`[일반] ${message}`); }}class UrgentNotification extends Notification { notify(message: string) { this.sender.sendMessage(`[긴급] ${message.toUpperCase()}`); }}Client
// 이메일 긴급 알림const emailUrgent = new UrgentNotification(new EmailSender());// 슬랙 긴급 알림const slackUrgent = new UrgentNotification(new SlackSender());// 슬랙 일반 알림const slackNormal = new NormalNotification(new SlackSender());emailUrgent.notify("server down!"); // 이메일 메시지: [긴급] SERVER DOWN!slackNormal.notify("daily report ready");// 슬랙 메시지: [일반] daily report ready위와 같이 브릿지 패턴으로 설계하면 언제든 일반 알림도 이메일로 보낼 수 있겠죠? 그리고 전송 채널로 전화 알림이 생겨면 새로운 채널을 추가하면 되요. 그리고 긴급/일반에 더해서 행사 알림이 추가되면 알림 종류만 추가하면 되고요. 둘 사이의 연결고리는 전송 채널 인터페이스를 알림 종류가 구성으로 사용하고 있어서 생긴다는 것을 통해 잘 이해하셨으면 좋겠어요.
마무리
브릿지 패턴을 쓰면 복잡한 조합 (n개의 알림, m개의 전송 채널)을 언제든 만들어 사용할 수 있어요. n개의 알림에 m개의 전송채널이 있으면 n * m개의 조합을 만들 수 있어요. 그리고 언제든 알림과 전송 채널을 추가할 수 있기 때문에 독립적인 확장성을 보장해요.
이 패턴은 사실 알게모르게 사용하는 경우가 많다고 생각해요. 예를 들면 게임 캐릭터가 공격을 하는 코드를 만든다고 해봐요. 그런데 공격이 어떤 무기를 쓰느냐에 따라 모션이 달라질 수 있어요. 그러면 "캐릭터 = Abstraction, 무기 = Implementor, 공격 모션 = Attack Strategy"로 구성해서 사용할 수 있을 것 같아요. 캐릭터의 공격 기능은 무기의 attack 구현을 사용하고요, 무기마다의 attack 구현은 Attack Strategy에 따라 다르게 적용되게할 수 있을 것 같아요. 이러면 캐릭터 n개와 무기 m개가 있으면 n*m개의 조합이 가능하겠죠?
아무튼 브릿지 패턴은 미래에 구조적 문제(조합의 폭발적 증가) 혹은 많은 확장이 예정되어 있을 때 사용하면 좋을 것 같아요. 게임 캐릭터와 무기가 딱 이 구조에 알맞다고 생각해서 예시를 들어봤어요.

