티스토리 뷰

개발/디자인 패턴

브릿지 패턴

-=HaeJuK=- 2024. 3. 5. 20:12
반응형

브릿지 패턴(Bridge Pattern)

브릿지 패턴(Bridge Pattern)은 구조적인 디자인 패턴 중 하나로, 추상화와 구현을 분리하여 서로 독립적으로 변화할 수 있도록 합니다. 이 패턴은 두 계층 사이의 '다리' 역할을 하여, 클라이언트 코드가 구현 부분에 직접적으로 의존하지 않도록 하며, 확장성과 유연성을 향상시킵니다. 브릿지 패턴은 크게 두 가지 구성 요소로 나뉩니다: 추상화(Abstraction)와 구현(Implementor)입니다.

 

추상화(Abstraction)

추상화는 고수준의 제어 논리를 정의합니다. 이것은 클라이언트가 사용하는 인터페이스로, 구현 부분에 대한 참조를 포함하여 구현 부분의 메서드를 호출합니다.

구현(Implementor)

구현 인터페이스는 추상화에서 정의된 메서드의 실제 구현을 제공합니다. 구현체는 추상화로부터 완전히 분리되어 있으며, 추상화를 통해 메서드가 호출됩니다.

 

예제: 그래픽 라이브러리와 UI

예를 들어, 다양한 그래픽 라이브러리를 사용하여 UI 요소를 그리는 애플리케이션을 개발한다고 가정해보겠습니다. 이 경우, 브릿지 패턴을 사용하여 그래픽 라이브러리(구현)와 UI 요소(추상화) 사이의 결합도를 낮출 수 있습니다.

// Implementor
class DrawingAPI {
public:
    virtual void drawCircle(double x, double y, double radius) = 0;
    virtual ~DrawingAPI() {}
};

// ConcreteImplementor 1
class DrawingAPI1 : public DrawingAPI {
public:
    void drawCircle(double x, double y, double radius) override {
        // DrawingAPI1의 원 그리기 구현
        cout << "API1.circle at " << x << ':' << y << ' ' << radius << endl;
    }
};

// ConcreteImplementor 2
class DrawingAPI2 : public DrawingAPI {
public:
    void drawCircle(double x, double y, double radius) override {
        // DrawingAPI2의 원 그리기 구현
        cout << "API2.circle at " << x << ':' << y << ' ' << radius << endl;
    }
};

// Abstraction
class Shape {
protected:
    DrawingAPI* drawingAPI;
public:
    Shape(DrawingAPI* drawingAPI) : drawingAPI(drawingAPI) {}
    virtual void draw() = 0; // 추상 메서드
    virtual ~Shape() {}
};

// RefinedAbstraction
class CircleShape : public Shape {
private:
    double x, y, radius;
public:
    CircleShape(double x, double y, double radius, DrawingAPI* drawingAPI)
        : Shape(drawingAPI), x(x), y(y), radius(radius) {}

    void draw() override {
        drawingAPI->drawCircle(x, y, radius);
    }
};

// 클라이언트 코드
int main() {
    DrawingAPI* api1 = new DrawingAPI1();
    DrawingAPI* api2 = new DrawingAPI2();
    Shape* circle1 = new CircleShape(1, 2, 3, api1);
    Shape* circle2 = new CircleShape(1, 2, 3, api2);

    circle1->draw(); // API1을 사용하여 그리기
    circle2->draw(); // API2를 사용하여 그리기

    delete api1;
    delete api2;
    delete circle1;
    delete circle2;

    return 0;
}

이 예제에서 DrawingAPI는 구현 인터페이스(Implementor) 역할을 하고, CircleShape는 추상화(Abstraction) 역할을 합니다. DrawingAPI1DrawingAPI2DrawingAPI의 구현체(ConcreteImplementor)입니다. Shape 클래스는 추상화를 정의하며, 구현체에 대한 참조를 통해 실제 동작을 구현체에 위임합니다. 이렇게 함으로써, UI 요소와 그래픽 라이브러리 사이의 결합도를 낮추고, 둘 사이의 독립적인 변화와 확장을 용이하게 합니다.

HAS-A 관계

HAS-A 관계로 소스 코드를 재구성하려면, 구현체(DrawingAPI1, DrawingAPI2)가 아닌, 구현 인터페이스(DrawingAPI)에 대한 참조를 Shape 클래스(또는 이를 상속받는 클래스)에 직접 포함시키는 방식으로 변경해야 합니다. 이 방식은 브릿지 패턴의 핵심 구조와 일치하지만, 여기서는 HAS-A 관계의 명확한 설명을 위해 기존 코드를 조금 더 명시적으로 조정할 것입니다.

원래의 브릿지 패턴 구현에서는 Shape 클래스가 DrawingAPI에 대한 참조를 가지고 있으므로, 이미 HAS-A 관계를 반영하고 있습니다. 그러나 이 관계를 더욱 명확히 하기 위해, Shape 클래스 내에서 DrawingAPI의 메서드를 사용하는 방식을 강조하고, CircleShape 클래스를 통해 이 관계를 활용하는 예를 다시 살펴보겠습니다.

// Implementor
class DrawingAPI {
public:
    virtual void drawCircle(double x, double y, double radius) = 0;
    virtual ~DrawingAPI() {}
};

// ConcreteImplementor 1
class DrawingAPI1 : public DrawingAPI {
public:
    void drawCircle(double x, double y, double radius) override {
        cout << "API1.circle at " << x << ':' << y << ' ' << radius << endl;
    }
};

// ConcreteImplementor 2
class DrawingAPI2 : public DrawingAPI {
public:
    void drawCircle(double x, double y, double radius) override {
        cout << "API2.circle at " << x << ':' << y << ' ' << radius << endl;
    }
};

// Abstraction
class Shape {
protected:
    DrawingAPI* drawingAPI; // HAS-A 관계

public:
    Shape(DrawingAPI* drawingAPI) : drawingAPI(drawingAPI) {}
    virtual void draw() = 0; // 추상 메서드
    virtual ~Shape() {}
};

// RefinedAbstraction
class CircleShape : public Shape {
private:
    double x, y, radius;

public:
    CircleShape(double x, double y, double radius, DrawingAPI* drawingAPI)
        : Shape(drawingAPI), x(x), y(y), radius(radius) {}

    void draw() override {
        drawingAPI->drawCircle(x, y, radius); // HAS-A 관계 활용
    }
};

// 클라이언트 코드
int main() {
    DrawingAPI* api1 = new DrawingAPI1();
    DrawingAPI* api2 = new DrawingAPI2();
    Shape* circle1 = new CircleShape(1, 2, 3, api1);
    Shape* circle2 = new CircleShape(1, 2, 3, api2);

    circle1->draw(); // API1을 사용하여 그리기
    circle2->draw(); // API2를 사용하여 그리기

    delete api1;
    delete api2;
    delete circle1;
    delete circle2;

    return 0;
}

IS-A 관계

IS-A 관계는 상속을 통해 표현되며, 이는 한 클래스가 다른 클래스의 서브타입(subtype)임을 나타냅니다. 예를 들어, "강아지는 동물이다(Dog IS-A Animal)"라는 관계는 Dog 클래스가 Animal 클래스의 서브클래스임을 의미합니다. 이 관계는 클래스 간의 계층적 관계를 정의하며, 다형성, 상속, 코드 재사용성 등 객체지향 프로그래밍의 핵심 원칙을 지원합니다.

브릿지 패턴의 예제 코드를 IS-A 관계로 변환하는 것은 브릿지 패턴의 목적과는 다소 거리가 있습니다. 브릿지 패턴의 목적은 추상화와 구현을 분리하는 것이기 때문입니다. 그러나, 예제 코드에서 CircleShape 클래스가 Shape 클래스를 상속받는 관계는 IS-A 관계의 예로 볼 수 있습니다.

다음은 기존의 브릿지 패턴 코드에서 IS-A 관계를 강조하는 방법입니다:

class Shape {
public:
    virtual void draw() = 0;
    virtual ~Shape() {}
};

class Circle : public Shape {
    // Circle IS-A Shape
public:
    void draw() override {
        // 원 그리기 구현
    }
};

여기서 Circle 클래스는 Shape 클래스의 서브클래스입니다. 즉, Circle IS-A Shape 관계가 성립합니다. Circle 클래스는 Shape의 모든 특성을 상속받으며, Shape가 정의한 draw 메서드를 구현합니다.

브릿지 패턴의 구현과는 별개로, IS-A 관계는 주로 상속을 통해 구현되며, 다형성을 활용한 코드 재사용과 확장성 향상에 기여합니다. 상속을 사용할 때는 서브클래스가 슈퍼클래스의 특성을 '있는 그대로' 상속받으며, 이를 통해 서브클래스는 슈퍼클래스의 모든 메서드와 속성에 접근할 수 있습니다. 하지만, 브릿지 패턴에서처럼 추상화와 구현을 분리하려는 목적으로는 HAS-A 관계가 더 적합합니다.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함