스택큐힙리스트

Bridge 패턴: 기능·구현을 갈라놓는 비밀 통로 본문

개발

Bridge 패턴: 기능·구현을 갈라놓는 비밀 통로

스택큐힙리스트 2025. 7. 17. 23:52
반응형

“화면 UI·비즈니스 로직은 계속 늘어나는데, 코드 변경할 때마다 여기저기 연쇄 폭탄이 터진다…”
프런트엔드 React와 백엔드 Spring Boot를 병행 개발하다 보면 자주 듣는 한숨입니다. Bridge 패턴은 이런 의존성 폭발을 막아 주는 구조(Structural) 패턴. 추상화(기능)와 구현을 ‘다리(Bridge)’로 연결해 둘을 완전히 독립시켜 주죠.


1️⃣ Bridge 패턴이 뭔가요?

  • 정의: 추상 인터페이스(Abstraction)와 실제 구현체(Implementor)를 별도 계층 으로 분리, 런타임에 조합하도록 해 주는 구조 패턴.
  • 효과: 기능·구현 계층을 따로 확장하므로 “새 기능 추가 ↔ 기존 구현 교체”를 마음껏 조합할 수 있습니다.
  • 자주 쓰는 상황
    • 플랫폼별(웹·모바일) UI 레이어가 같지만 렌더링 엔진이 다를 때
    • 결제·로그 수집 등 벤더 교체 가능성을 열어 두고 싶을 때
    • 복잡한 상속 트리 때문에 클래스 폭발이 일어날 때

2️⃣ React 예시 – 뷰 라이브러리 교체에도 끄떡없는 버튼 컴포넌트

// Implementor 역할 – 실제 렌더링 전략
export interface ButtonRenderer {
  render(label: string, onClick: () => void): JSX.Element;
}

export class MaterialUIButton implements ButtonRenderer {
  render(label, onClick) {
    return <MuiButton onClick={onClick}>{label}</MuiButton>;
  }
}

export class TailwindButton implements ButtonRenderer {
  render(label, onClick) {
    return (
      <button
        className="rounded-xl px-4 py-2 bg-indigo-500 text-white"
        onClick={onClick}
      >
        {label}
      </button>
    );
  }
}

// Abstraction 역할 – ‘기능’만 정의
export class BridgeButton {
  constructor(private renderer: ButtonRenderer) {}
  draw(label: string, action: () => void) {
    return this.renderer.render(label, action);
  }
}

// 사용 – 런타임에 조합!
const SaveButton = new BridgeButton(new MaterialUIButton());
// 또는 프리미엄 테마
// const SaveButton = new BridgeButton(new TailwindButton());

버튼 기능은 그대로 두고, 외관 구현만 바꿔 꼽으면 끝. 덕분에 디자인 시스템 변경에도 컴포넌트 로직은 단 한 줄도 손대지 않습니다.


3️⃣ Spring Boot 예시 – DB 벤더 교체를 위한 Repository Bridge

// Implementor – DB 연동 전략
public interface AccountStore {
    AccountEntity findById(String id);
    void save(AccountEntity entity);
}

@Repository
@RequiredArgsConstructor
public class MySQLAccountStore implements AccountStore {
    private final JdbcTemplate jdbc;
    // ...구체 SQL 구현
}

@Repository
@RequiredArgsConstructor
public class MongoAccountStore implements AccountStore {
    private final MongoTemplate mongo;
    // ...Mongo 쿼리 구현
}

// Abstraction – 도메인 서비스
@Service
@RequiredArgsConstructor
public class AccountService {
    private final AccountStore store;   // 다리를 건넌다!

    public Account find(String id) {
        return Account.from(store.findById(id));
    }
}

JPA → MongoDB 로 갈아타야 할 때? AccountStore 구현체만 주입 교체하면 도메인 로직은 그대로 보존됩니다.


4️⃣ 구현 꿀팁 •🔥

  1. 조합(Composition) 우선: 다형성을 위한 상속 대신 인터페이스 + 의존성 주입을 쓰면 테스트 대역(Mock) 작성이 편리합니다.
  2. 런타임 스위칭: 스프링에서는 @Profile, React에서는 DI 컨테이너·Context를 이용해 구현체를 조건부로 주입하세요.
  3. Adapter·Facade와 함께: 외부 API 형태가 들쑥날쑥하다면 Adapter로 맞춘 뒤 Bridge로 조합하면 더 깔끔합니다.
  4. 네이밍 규칙: *Renderer, *Store, *Gateway 처럼 Implementor를 한눈에 구분되는 접미사로 통일하면 유지보수성이 올라갑니다.

5️⃣ 장·단점 요약

  • 👍 확장성: 기능/구현 각각 선형적으로 늘어날 수 있어 클래스 폭발을 막습니다.
  • 👍 리팩터링 유연성: 구현 기술 교체(예: MySQL → Mongo) 시 비즈니스 코드 무변경.
  • ⚠️ 설계 난이도: 초기 구조를 잘못 잡으면 ‘다리 위의 다리’처럼 과도한 추상화가 될 수 있으니, 변경 가능성이 높은 축에만 적용하세요.

한 줄 요약

Bridge 패턴은 “변화할 축을 물 위·물 아래로 갈라 놓고, 다리만 건너는” 전략입니다. 기능과 구현을 떼어내면, 당신의 코드베이스는 언제든 새 길을 놓을 준비가 돼요.

반응형
Comments