[디자인패턴] 구조패턴(2) - Bridge Pattern (브릿지 패턴)

Study/Software Architecture · 2023. 5. 23. 11:33

디자인 패턴 총정리 시리즈의 일곱 번째 패턴은 Bridge Pattern입니다.

 

소프트웨어를 설계할 때 구현을 하고 끝이면 좋겠지만, 추상화, 은닉, 느슨한 결합, 캡슐화 등등 신경 써야 할 것이 많습니다.

이때 Bridge 패턴은 구현에서 추상부를 분리하여 각각 별개의 클래스로 수정할 수 있도록 합니다. 이와 같이 구현과 추상부 간의 느슨한 결합을 만들어주는 Bridge를 사용하면 한쪽 계층의 변경을 다른 한쪽으로부터 독립적으로 유지할 수 있습니다.

 

해당 게시물에서 이러한 Bridge 패턴에 대한 간단한 개념과 사용 예시, 장/단점을 알아보겠습니다.

 

1. 개요

Bridge 패턴은 추상화 해야할 부분을 구현에서 분리하고 각 부분을 개별적으로 변경하고 수정할 수 있도록 하는 구조패턴입니다. 이를 통해 추상화용과 구현용 두 가지 클래스 계층을 만들어 관리할 수 있어 계층 간 독립성을 향상할 수 있습니다.

 

해당 패턴은 Abstraction, Implementor, ConcreteImplementor 3가지 주요 컴포넌트로 구성되어 있습니다.

 

2. 예시

다음은 Bridge 패턴을 사용하여 도면 어플리케이션을 개발하는 시나리오를 예로 들어보겠습니다.

해당 예시는 원, 정삼각형, 삼각형 등 형상이 다른 도면을 작성해야 하고, OpenGL 및 DirectX와 같은 여러 렌더링 플랫폼을 지원해야 합니다.

 

Bridge 패턴을 사용하여 해당 시나리오를 구현하기 위해 2개의 클래스 계층을 정의합니다.하나는 Shape 용, 또 다른 하나는 Rendering Platform 용입니다.

class Renderer {
public:
    virtual void renderCircle(double x, double y, double radius) = 0;
    virtual void renderSquare(double x, double y, double side) = 0;
    // ... other render methods for different shapes
};

class OpenGLRenderer : public Renderer {
public:
    void renderCircle(double x, double y, double radius) override {
        // OpenGL-specific rendering logic for circles
    }

    void renderSquare(double x, double y, double side) override {
        // OpenGL-specific rendering logic for squares
    }
    // ... implementation of other render methods
};

class DirectXRenderer : public Renderer {
public:
    void renderCircle(double x, double y, double radius) override {
        // DirectX-specific rendering logic for circles
    }

    void renderSquare(double x, double y, double side) override {
        // DirectX-specific rendering logic for squares
    }
    // ... implementation of other render methods
};

class Shape {
protected:
    Renderer* renderer;

public:
    Shape(Renderer* renderer) : renderer(renderer) {}

    virtual void draw() = 0;
};

class Circle : public Shape {
private:
    double x, y, radius;

public:
    Circle(double x, double y, double radius, Renderer* renderer)
        : Shape(renderer), x(x), y(y), radius(radius) {}

    void draw() override {
        renderer->renderCircle(x, y, radius);
    }
};

class Square : public Shape {
private:
    double x, y, side;

public:
    Square(double x, double y, double side, Renderer* renderer)
        : Shape(renderer), x(x), y(y), side(side) {}

    void draw() override {
        renderer->renderSquare(x, y, side);
    }
};

int main() {
    // Create renderers
    Renderer* openGLRenderer = new OpenGLRenderer();
    Renderer* directXRenderer = new DirectXRenderer();

    // Create shapes
    Shape* circle = new Circle(2.0, 3.0, 5.0, openGLRenderer);
    Shape* square = new Square(1.0, 1.0, 4.0, directXRenderer);

    // Draw shapes
    circle->draw();
    square->draw();

    // Clean up
    delete openGLRenderer;
    delete directXRenderer;
    delete circle;
    delete square;

    return 0;
}

해당 예시 시나리오에서 서로 다른 모양을 나타내는 Shape 클래스와 서로 다른 렌더링 플랫폼을 나타내는 Render 클래스 계층이 있습니다. Shapebase 클래스는 Render 인터페이스에 대한 참조를 유지하고 Renderer가 제공하는 특정 Shape을 구현하는 메서드를 사용하여 그림을 그릴 수 있습니다.

 

Shape 클래스는 도형 추상화(circle, square)와 Renderer 클래스 사이의 브리지 역할을 합니다. Shape 클래스는 Renderer 객체에 대한 참조 인스턴스를 보유하고, Renderer 인터페이스가 가지고 있는 메서드에 렌더링 작업을 위임합니다.

 

3. 장점

Decoupling: Bridge 패턴은 추상화와 구현 사이의 느슨한 결합을 만들어주고, 해당 계층들을 독립적으로 변경 및 수정할 수 있습니다. 따라서, 계층 한쪽이 변경되어도 다른 쪽 계층은 영향을 받지 않기 때문에 코드의 유연성이 높아지고 유지보수가 쉬워지게 됩니다.

Extensibility: Bridge 패턴을 사용하면 기존 코드를 변경하지 않고, 새로운 요소를 추가할 수 있습니다. (위의 새로운 shape와 rendering platform) 이를 통해 시스템 확장성을 향상할 수 있습니다.

Improved Testability: 추상화를 구현에서 분리하기 때문에 각 컴포넌트를 개별적으로 테스트하기 쉬워집니다.

 

4. 단점

Increased Complexity:  Bridge 패턴을 구현하면 소규모 시스템이나 단순한 시나리오에서는 복잡성이 증가할 수 있습니다.

Code Overhead: Bridge 패턴에서는 추가 클래스와 인터페이스를 정의해야 하기 때문에 이로 인해 코드 크기가 증가할 수 있습니다. 

 

5. 결론

Bridge 패턴은 추상화를 구혆에서 분리하고 독립적으로 관리하기에 좋은 패턴입니다. 해당 패턴을 사용함으로써 유지보수성, 확장성, 테스트 효율이 높은 소프트웨어 시스템을 구축할 수 있습니다. 하지만 코드 복잡도와 크기 등에서 trade-off가 있기 때문에 해당 프로젝트에 유용한 패턴인지 고려하는 것이 중요합니다.

반응형