mojo's Blog

Bridge Pattern 본문

Design Patterns

Bridge Pattern

_mojo_ 2024. 6. 24. 10:30

Bridge Pattern

 

목적: 구현 객체 구조와 독립적인 추상 객체 구조를 정의하여 결합을 제한한다.

사용 시기

- 추상화와 구현이 컴파일 시점에 묶여서는 안됨

- 추상화와 구현이 독립적으로 확장 가능

- 추상의 구현 변경이 클라이언트에 영향을 주어서는 안됨

- 구현 세부 사항은 클라이언트로부터 숨겨져야 함

 

※ Initial Problem

간단한 그림 그리기 프로그램이 있다고 가정한다.

(1) 사각형을 그릴 수 있음

(2) 두 가지 그림 라이브러리 중 하나를 사용할 수 있음

      설정에 따라 그림 프로그램 1(DP1) 또는 그림 프로그램 2(DP2) 사용이 결정됨

      DP1: draw_a_line(x1, y1, x2, y2)

      DP2: drawline(xy, x2, y1, y2)

     

 

추상 클래스 "Rectangle" 을 사용함으로써, 다양한 종류의 사각형들 간의 유일한 차이점이

"drawLine" 메서드를 어떻게 구현할 지에 있다는 점을 활용할 수 있다.

V1Rectangle 에서는 DP1 라이브러리의 draw_a_line() 메서드를 사용하며,

V2Rectangle 에서는 DP2 라이브러리의 drawline() 메서드를 활용한다.

 

※ Requirement Changes

이제 사각형 말고 원도 그릴 수 있어야 한다.

컬렉션 객체는 사각형과 원의 차이를 알 필요가 없다.

클래스 계층 구조에 새로운 레벨을 추가하여 이전 접근 방식을 간단히 확장할 수 있다.

- "Shape" 클래스를 추가하여, 이 클래스에서 "Rectangle", "Circle" 클래스를 파생

- 클라이언트 객체는 단순히 "Shape" 객체를 참조 (어떤 종류의 도형이 주어졌는지는 걱정 x)

 

 

 

abstract class Shape {
	abstract public void draw();
}
abstract class Circle extends Shpae{
    public void draw() {
    	drawCircle(x, y, r);
    }
    abstract protected void drawCircle(double x, double y, double r);
}
class V1Circle extends Circle {
    protected void drawCircle() {
    	DP1.draw_a_circle(x, y, r);
    }
}
class V2Circle extends Circle {
    protected void drawCircle() {
    	DP2.drawcircle(x, y, r);
    }
}
abstract class Rectangle extends Shape {
    public void draw() {
        drawLine(_x1, _y1, _x2, _y1);
        drawLine(_x2, _y1, _x2, _y2);
        drawLine(_x2, _y2, _x1, _y2);
        drawLine(_x1, _y2, _x1, _y1);
    }
    abstract protected voiddrawLine(double x1, double y1,
    	double x2, double y2);
}
class V1Rectangle extends Rectangle {
    protected void drawLine(double x1, double y1,double x2, double y2) {
    	DP1.draw_a_line(x1, y1, x2, y2);
    }
}
class V2Rectangle extends Rectangle {
    protected void drawLine(double x1, double y1,double x2, double y2) {
    	DP2.drawline(x1, x2, y1, y2);
    }
}

 

만약 다른 그림 프로그램을 얻는다면 구현의 변형이 생길 수 있다. (ex: DP3)

=> 2가지 도형 x 3가지 구현 = 6가지 조합

위 경우에서 만약 다른 종류의 도형을 얻는다면 개념의 변형이 생긴다. (ex: Triangle)

=> 3가지 도형 x 3가지 구현 = 9가지 조합

조합이 늘어나는 문제가 발생한다.

 

※ Problem Analysis

문제점

- 추상화와 구현의 결합이 강하여 조합이 늘어남

- 각 도형 타입은 자신이 사용하고 있는 그림 프로그램 타입을 알아야 함

해결책

- 추상화의 변형과 구현의 변형을 분리함으로써 클래스 수가 선형적으로 증가되도록 변경해야 함

 

 

위와 같이 구조를 변경하더라도 생기는 문제점

- 모양이 추가될 때마다 비선형적으로 늘어남

- 삼각형이 추가될 경우 V1Shape, V2Shape 에서 삼각형 클래스를 파생시켜야 함

- 여전히 동일한 조합적 문제를 가지고 있음

 

※ Analyzing the problem

- Commonality analysis (공통 분석)

- Variability analysis (변형 분석)

공통 개념은 추상 클래스에 의해 표현되며, 변형은 구체 클래스에 의해 구현된다.

 

※ Commonality analysis

서로 다른 종류의 도형과 서로 다른 종류의 그림 프로그램을 가지고 있다.

공통 개념은 "Shape"와 "Drawing" 이므로, 이를 캡슐화한다.

- Shape: +draw()

- Drawing: +drawLine() / +drawCircle()

 

※ Variability Analysis

도형의 경우, 사각형과 원을 나타낸다. 

그림 프로그램의 경우 각각 DP1, DP2 를 나타낸다.

 

※ Relating each other

클래스들 간의 관계를 설정하며 하나가 다른 하나를 사용하는 방식을 선택한다.

(1) 그림 프로그램이 도형을 사용하는 경우

      그림 프로그램이 도형을 직접 그릴 수 있다면, 그림 프로그램은 도형이 어떻게 생겼는지 알아야 한다.

      이는 객체의 기본 원칙을 위반한다. (SRP)

      또한 캡슐화를 위반한다. (도형에 대한 구체 정보를 알아야 함)

(2) 도형이 그림 프로그램을 사용하는 경우

      도형이 그림 객체를 사용하여 자신을 그린다.

      도형은 어떤 종류의 그림 객체를 사용하는지 알 필요가 없다. (그림 클래스를 참조하기 때문)

 

 

클래스 상속 대신에 객체 구성을 사용하였다. (Object Composition)

추상화와 구현을 분리하여 두 요소가 독립적으로 변화할 수 있도록 한다.

위 구조를 코드로 변경하면 아래와 같다.

abstract class Shape {
    abstract public void draw();
    private Drawing _dp;
    Shape (Drawing dp) {
    	_dp= dp;
    }
    public void drawLine(double x1, double y1, double x2, double y2) {
    	_dp.drawLine(x1,y1,x2,y2);
    }
    public void drawCircle(double x, double y, double r) {
    	_dp.drawCircle(x,y,r);
    }
}

abstract public class Drawing {
    abstract public void drawLine(double x1, double y1,double x2, 
    	double y2);
    abstract public void drawCircle(double x, double y, double r);
}

class V1Drawing extends Drawing {
    public void drawLine(double x1, double y1, double x2, double y2) {
    	DP1.draw_a_line(x1,y1,x2,y2);
    }
    public void drawCircle(double x, double y, double r) {
    	DP1.draw_a_circle(x,y,r);
    }
}
class V2Drawing extends Drawing {
    public void drawLine(double x1, double y1, double x2, double y2) {
    	DP2.drawline(x1,x2,y1,y2);
    }
    public void drawCircle(double x, double y, double r) {
    	DP2.drawcircle(x,y,r);
    }
}

class Rectangle extends Shape {
    public Rectangle (Drawing dp, double x1, double y1,double x2, double y2) {
        super(dp);
        _x1 = x1; _x2 = x2; _y1 = y1; _y2 = y2;
    }
    public void draw() {
        drawLine(_x1,_y1,_x2,_y1);
        drawLine(_x2,_y1,_x2,_y2);
        drawLine(_x2,_y2,_x1,_y2);
        drawLine(_x1,_y2,_x1,_y1);
    }
}
class Circle extends Shape {
    public Circle (Drawing dp, double x, double y, double r) {
        super(dp);
        _x = x; _y = y; _r = r;
    } 
    public void draw(){
        drawCircle(_x,_y,_r);
    } 
}

 

※ Bridge Pattern

 

 

- Abstraction(or RefinedAbstraction)는 어떤 엔티티에 대한 고수준 제어 계층

- 이 계층 자체가 실제 작업을 수행하지 않음

- 작업을 구현 계층으로 위임

 

※ Comparison with Adapter

유사점

- 두 패턴 모두 기본 구현의 세부 사항을 숨기기 위해 사용됨

차이점

- Adapter 

  • 서로 관련 없는 구성 요소들이 함께 작동하도록 하는 데 중점
  • 시스템이 설계된 후에 적용됨

- Bridge

  • 추상화와 구현이 독립적으로 변할 수 있도록 설계 초기 단계에서 사용
  • "확장 가능한 시스템"을 설계함

구조적 차이점

- 브리지는 복잡한 엔티티를 구현으로부터 추상화할 수 있음

- 어댑터는 단일 인터페이스만 추상화함

'Design Patterns' 카테고리의 다른 글

Composite Pattern  (3) 2024.06.09
Adapter Pattern  (0) 2024.06.09
Decorator Pattern  (0) 2024.06.09
Singleton Pattern  (1) 2024.06.09
Builder Pattern  (0) 2024.06.09
Comments