← Posts

GoF · Structural · Bridge

Bridge

두 축이 곱해지며 클래스가 폭발할 때. '무엇'과 '어떻게'를 두 계층으로 갈라 다리로 잇고, 각자 독립해 자라게 하는 구조 패턴.

목차
  1. 두 축이 곱해지며 클래스가 폭발한다
  2. 브리지 = 두 축을 갈라 다리로 잇는다
  3. 무엇을 두 축으로 보나
  4. 구현 쪽을 인터페이스로
  5. 추상 쪽이 구현을 참조로 든다
  6. 도형이 렌더러에게 위임한다
  7. 두 축이 따로 자란다
  8. 어댑터와 무엇이 다른가
  9. 실무: 두 차원이 독립으로 변할 때
  10. 정리

두 축이 곱해지며 클래스가 폭발한다

도형을 그린다고 하자. 도형은 원·사각형이 있고, 그리는 방식은 벡터·래스터(픽셀)가 있다. 상속으로 조합하면 이렇게 된다.

java
class VectorCircle  extends Circle { ... }
class RasterCircle  extends Circle { ... }
class VectorSquare  extends Square { ... }
class RasterSquare  extends Square { ... }

도형 종류 × 그리는 방식만큼 클래스가 생긴다. 도형이 3개, 방식이 3개면 벌써 9개다. 도형을 하나 더하면 방식 수만큼, 방식을 하나 더하면 도형 수만큼 클래스가 는다. 두 축이 곱해지며 폭발한다.

브리지 = 두 축을 갈라 다리로 잇는다

브리지 패턴이 이 곱셈을 덧셈으로 바꾼다.

서로 다른 두 축을 별도의 계층 둘로 갈라내고, 하나가 다른 하나를 **참조(다리)**로 들게 한다. 그러면 두 축이 독립적으로 변할 수 있다.

도형과 그리는 방식을 한 클래스에 묶지 않는다. 도형은 도형끼리, 방식은 방식끼리 따로 두고, 도형이 방식을 하나 골라 들게 한다. 이 참조가 두 계층을 잇는 다리다.

무엇을 두 축으로 보나

가르는 기준은 **“무엇”과 “어떻게”**다.

  • 추상(abstraction) - 무엇을 하는가. 여기선 “도형”(원·사각형). 고수준의 개념이다.
  • 구현(implementation) - 그걸 어떻게 해내는가. 여기선 “그리는 방식”(벡터·래스터). 저수준의 수단이다.

이 둘은 서로 독립적으로 변한다. 새 도형이 생기는 것과 새 렌더링 방식이 생기는 건 별개의 사정이다. 그래서 한 클래스에 묶으면 곱셈이 되고, 갈라두면 각자 는다.

구현 쪽을 인터페이스로

먼저 “어떻게”를 인터페이스로 세운다.

java
public interface Renderer {
    void renderCircle(float radius);
}

public class VectorRenderer implements Renderer {
    public void renderCircle(float radius) { /* 벡터로 그린다 */ }
}
public class RasterRenderer implements Renderer {
    public void renderCircle(float radius) { /* 픽셀로 그린다 */ }
}

Renderer가 구현 계층이다. 벡터든 래스터든 “원을 그려라”에 각자의 방식으로 답한다.

추상 쪽이 구현을 참조로 든다

이제 “무엇”인 도형이 Renderer를 하나 들고 있게 한다. 이 참조가 다리다.

java
public abstract class Shape {
    protected final Renderer renderer;      // 다리

    protected Shape(Renderer renderer) {
        this.renderer = renderer;
    }

    public abstract void draw();
}

Shape는 자기가 벡터인지 래스터인지 모른다. 그냥 renderer에게 그리기를 맡길 뿐이다.

도형이 렌더러에게 위임한다

구체 도형은 자기 모양을 알고, 그리는 일은 렌더러에 넘긴다.

java
public class Circle extends Shape {
    private final float radius;

    public Circle(Renderer renderer, float radius) {
        super(renderer);
        this.radius = radius;
    }

    @Override
    public void draw() {
        renderer.renderCircle(radius);      // 어떻게 그릴지는 renderer가 안다
    }
}

쓰는 쪽은 도형과 방식을 조립한다.

java
Shape circle = new Circle(new VectorRenderer(), 5);
circle.draw();     // 벡터로 그린 원

Shape raster = new Circle(new RasterRenderer(), 5);
raster.draw();     // 픽셀로 그린 원

Circle은 딱 하나뿐이다. 벡터로 그리든 래스터로 그리든, 어떤 렌더러를 넣느냐로 정해진다.

두 축이 따로 자란다

이게 곱셈을 덧셈으로 바꾼 결과다.

diagramdiagram
  • 새 도형(삼각형)을 더하고 싶으면 → Triangle 하나만 만든다. 모든 렌더러와 자동으로 조합된다.
  • 새 방식(OpenGL)을 더하고 싶으면 → OpenGLRenderer 하나만 만든다. 모든 도형이 그걸 쓸 수 있다.

도형 N개 × 방식 M개가 N × M개 클래스가 아니라 N + M로 끝난다. 두 축이 서로를 안 건드리고 각자 자란다.

어댑터와 무엇이 다른가

브리지도 참조로 위임하니 어댑터와 닮았다. 하지만 시점과 의도가 다르다.

  • 브리지미리 설계한다. 두 축이 독립적으로 변할 걸 내다보고, 처음부터 갈라 둔다. 폭발을 예방하는 것이다.
  • 어댑터나중에 맞춘다. 이미 있는 두 가지가 인터페이스가 안 맞을 때, 사이에 끼워 붙게 한다. 이미 벌어진 불일치를 수습하는 것이다.

브리지는 계획적 분리, 어댑터는 사후 맞춤이다.

실무: 두 차원이 독립으로 변할 때

브리지의 발상은 실무 곳곳에 있다. JDBC가 대표적이다. 앱은 java.sql API(추상)로 짜고, 실제 DB 접속은 각 벤더의 드라이버(구현)가 한다. 앱 코드를 안 바꾸고 드라이버만 갈면 다른 DB로 붙는다 - API와 드라이버라는 두 축이 독립이다. 로깅에서 SLF4J(추상 API)와 그 뒤의 구현(logback·log4j)이 갈린 것도 같은 구조다.

핵심 신호는 하나다. 독립적으로 변하는 두 차원이 곱해져 클래스가 늘고 있다면, 그 둘을 갈라 다리로 잇는다.

정리

브리지란’무엇’과 ‘어떻게’를 두 계층으로 갈라 참조로 잇는다
문제독립적인 두 축을 상속으로 묶으면 N×M로 폭발한다
고침구현을 인터페이스로 빼고, 추상이 그걸 참조(다리)로 든다
효과두 축이 따로 자란다 - N×MN+M으로
어댑터와브리지=미리 설계한 분리, 어댑터=사후 맞춤
실무JDBC(API×드라이버)·SLF4J(API×구현) 같은 두 축 분리

곱해져 폭발하는 두 축을, 미리 갈라 다리로 잇는 것. 그래서 각자 독립해 자라게 하는 것 - 그게 브리지다.

관련 글