브리지 패턴
알림 종류와 전송 방식처럼 독립적으로 늘어나는 두 축을 나누고 객체로 연결하는 구조 패턴.
· 마지막 업데이트: 2026-10-25 · 7 min read
개요
일반 알림과 긴급 알림을 문자와 메일로 보낸다고 하자. 조합마다 클래스를 만들면 일반문자·긴급문자·일반메일·긴급메일이 각각 생기고, 채널을 하나 더 붙일 때마다 알림 종류의 수만큼 구현이 늘어난다.
여기서 채널은 문자·메일처럼 메시지를 전달하는 경로다. 긴급 표시를 붙일지는 알림 쪽 규칙이고, 그 문장을 어떻게 보낼지는 채널이 맡는다. 브리지(Bridge)는 이렇게 따로 바뀌는 두 역할을 갈라 두고, 알림 객체가 채널 객체를 참조하도록 이어 준다.
UML
Notice는 IChannel을 참조하며, 알림 쪽은 NormalNotice와 UrgentNotice가, 전송 쪽은 SmsChannel과 MailChannel이 확장한다. 어느 채널에 붙을지는 Notice의 생성자에서 정해진다.
역사
GoF는 1994년 Design Patterns에서 Bridge를 구조 패턴으로 정리했다. 추상화와 구현을 분리해 각각 확장할 수 있게 하는 구조다.1
왜 사용하는가?
긴급 문구 하나를 바꾸자고 문자·메일 구현을 모두 손대거나, 푸시 알림을 붙이자고 일반·긴급 규칙을 다시 쓰고 싶지는 않다. Notice가 쓰는 것은 채널에 메시지를 건네는 공통 약속인 IChannel 하나뿐이라, 알림 규칙과 전달 방식은 서로 다른 쪽에서 각각 바뀐다.
어댑터가 이미 다른 약속을 가진 대상을 끼워 맞추는 데 초점을 둔다면, 브리지는 처음부터 두 축을 독립적으로 설계한다. 둘 다 객체를 감싸는 모양이 나오므로, 구별은 모양이 아니라 해결하려는 문제로 한다.
예제
일반 알림을 문자 채널에 붙여 본다. 전송 클래스는 실제로 통신하지 않고 결과 문자열만 돌려준다.
using System;
using System.Collections.Generic;
using System.Linq;
Console.WriteLine(new NormalNotice(new SmsChannel()).Send("ready"));
// 알림 쪽이 문자·메일의 구체 API를 몰라도 메시지를 건넬 수 있는 약속이다.
interface IChannel { string Deliver(string message); }
// 실제 전송 대신 어느 채널을 골랐는지 문자열로 보여 준다.
sealed class SmsChannel : IChannel
{
public string Deliver(string message) => "SMS: " + message;
}
sealed class MailChannel : IChannel
{
public string Deliver(string message) => "MAIL: " + message;
}
abstract class Notice(IChannel channel)
{
// 생성자로 받은 채널을 보관한다. 알림 종류와 전달 방식을 여기서 연결한다.
protected IChannel Channel { get; } = channel;
public abstract string Send(string message);
}
sealed class NormalNotice(IChannel channel) : Notice(channel)
{
public override string Send(string message) => Channel.Deliver(message);
}
sealed class UrgentNotice(IChannel channel) : Notice(channel)
{
// 긴급 문구는 알림 규칙이므로 채널마다 복사하지 않고 이곳에서 붙인다.
public override string Send(string message) => Channel.Deliver("URGENT " + message);
}
SMS: readynew NormalNotice(new SmsChannel())에서 바깥 객체는 알림 종류를, 안쪽 객체는 전달 경로를 정한다. 문자 API를 직접 부르는 쪽은 NormalNotice가 아니라, 메시지를 IChannel.Deliver()에 넘기고 나머지는 채널이 처리한다.
범위 넓힌 예제
알림 두 종류에 채널 두 종류를 곱해 본다. 긴급 표시를 붙이는 규칙은 UrgentNotice 하나에만 들어 있다.
using System;
using System.Collections.Generic;
using System.Linq;
// 채널만 교체하면서 같은 두 알림 종류를 재사용한다.
foreach (IChannel channel in new IChannel[] { new SmsChannel(), new MailChannel() })
{
Console.WriteLine(new NormalNotice(channel).Send("ready"));
Console.WriteLine(new UrgentNotice(channel).Send("ready"));
}
// 알림 쪽이 문자·메일의 구체 API를 몰라도 메시지를 건넬 수 있는 약속이다.
interface IChannel { string Deliver(string message); }
// 실제 전송 대신 어느 채널을 골랐는지 문자열로 보여 준다.
sealed class SmsChannel : IChannel
{
public string Deliver(string message) => "SMS: " + message;
}
sealed class MailChannel : IChannel
{
public string Deliver(string message) => "MAIL: " + message;
}
abstract class Notice(IChannel channel)
{
// 생성자로 받은 채널을 보관한다. 알림 종류와 전달 방식을 여기서 연결한다.
protected IChannel Channel { get; } = channel;
public abstract string Send(string message);
}
sealed class NormalNotice(IChannel channel) : Notice(channel)
{
public override string Send(string message) => Channel.Deliver(message);
}
sealed class UrgentNotice(IChannel channel) : Notice(channel)
{
// 긴급 문구는 알림 규칙이므로 채널마다 복사하지 않고 이곳에서 붙인다.
public override string Send(string message) => Channel.Deliver("URGENT " + message);
}
SMS: ready
SMS: URGENT ready
MAIL: ready
MAIL: URGENT ready네 조합을 담으려고 전용 클래스를 네 개 만들지는 않았다. 다만 실제 전송에 쓰려면 실패·취소·재시도에 대한 계약이 더 필요하고, 여기 나오는 대역은 외부로 메시지를 보내지 않는다.
TypeScript 예제
TypeScript로 옮겨도 두 역할을 나누는 방식은 그대로다. 위 UML의 Send·Deliver가 아래 코드에서는 send·deliver에 대응하고, 생성자로 채널을 넘기는 구조도 같다.
// 알림 쪽이 문자·메일의 구체 API를 몰라도 메시지를 건넬 수 있는 약속이다.
interface IChannel {
deliver(message: string): string;
}
// 실제 전송 대신 어느 채널을 골랐는지 문자열로 보여 준다.
class SmsChannel implements IChannel {
deliver(message: string): string { return "SMS: " + message; }
}
class MailChannel implements IChannel {
deliver(message: string): string { return "MAIL: " + message; }
}
abstract class Notice {
protected readonly channel: IChannel;
constructor(channel: IChannel) {
// 생성자로 받은 채널을 보관한다. 알림 종류와 전달 방식을 여기서 연결한다.
this.channel = channel;
}
abstract send(message: string): string;
}
class NormalNotice extends Notice {
override send(message: string): string {
return this.channel.deliver(message);
}
}
class UrgentNotice extends Notice {
override send(message: string): string {
// 긴급 문구는 알림 규칙이므로 채널마다 복사하지 않고 이곳에서 붙인다.
return this.channel.deliver("URGENT " + message);
}
}
// 채널만 교체하면서 같은 두 알림 종류를 재사용한다.
for (const channel of [new SmsChannel(), new MailChannel()]) {
console.log(new NormalNotice(channel).send("ready"));
console.log(new UrgentNotice(channel).send("ready"));
}SMS: ready
SMS: URGENT ready
MAIL: ready
MAIL: URGENT ready새 채널이 deliver()만 구현하면 NormalNotice와 UrgentNotice 어디에든 그대로 연결된다. 반대 방향도 마찬가지여서, 알림 종류를 늘려도 기존 채널을 수정할 필요가 없다.
같이 보기
C# 예제는 저마다 독립된 .NET 10 콘솔 프로그램이다. TypeScript 예제는 컴파일한 JavaScript를 Node.js에서 실행하면 된다.