구현에서 추상을 분리하여 이들이 독립적으로 다양성을 가질 수 있도록 한다.
Motivation
기존 방식에서는 Window 클래스가 특정 플랫폼(Linux, Mac, Windows)에 대한 구현을 직접 포함했다면, 새로운 플랫폼이 추가될 때마다 Window 클래스를 수정해야 한다.
브릿지 패턴을 이용해 Window와 WindowImp를 분리하면 독립적으로 확장할 수 있게 된다.
Applicability
- 추상적 개념과 이에 대한 구현 사이 지속적인 종속 관계를 피하고 싶을 때
- 추상적 개념과 구현 모두가 독립적으로 서브클래싱을 통해 확장되어야 할 때
- 추상적 개념에 대한 구현 내용을 변경하는 것이 다른 관련 프로그램에 아무런 영향을 주지 않아야 할 때
- 사용자에게 구현을 완벽하게 은닉하길 원할 때
- 클래스 계통에서 클래스 수가 급증하는 것을 방지하고자 할 때
- 여러 객체들에 걸쳐 구현을 공유하고자 하며(참조 카운트 등), 또 이런 사실을 사용하는 쪽에 공개하고 싶지 않을 때
Structure

Abstraction: 추상적 개념에 대한 인터페이스를 제공하고 객체 구현자에 대한 참조자를 관리한다.Refined Abstraction: 추상적 개념에 정의된 인터페이스를 확장한다.Implementor: 구현 클래스에 대한 인터페이스를 제공한다. 즉, 실질적인 구현을 제공한 서브클래스들에 공통적인 시그니처만을 정의한다.ConcreteImplementor:Implementor인터페이스를 구현한 것으로 실질적인 구현 내용을 담고 있다.
Collaborations
Abstraction클래스가 사용자 요청을Implementor객체에 전달한다.
Consequences
-
인터페이스와 구현 분리.
구현이 인터페이스에 얽매이지 않는다. 추상적 개념에 대한 어떤 방식의 구현을 택할지는 런타임에 결정될 수 있다. 즉, 런타임에 어떤 객체가 자신의 구현을 수시로 변경할 수 있음을 의미한다.
-
확장성 제고.
Abstraction과Implementor를 독립적으로 확장할 수 있다. -
구현 세부사항 사용자에게서 숨기기.
Implementation
-
Implementor하나만 둔다.구현 방법이 하나인 경우
Implementor를 추상 클래스로 정의하는 것은 불필요하다. C++에서는Implementor클래스의 인터페이스를 사용자에게 보이지 않도록private영역에 정의하면 된다. -
정확한
Implementor객체를 생성한다.Abstraction클래스가 사용할ConcreteImplementor를 직접 생성한다. 생성자에서 매개변수를 받아 적절한 구현체를 선택할 수 있다.팩토리 패턴을 활용해 객체 생성을 위임할 수 있다.
Abstraction클래스가 직접Implementor객체를 생성하지 않고, 대신 팩토리를 통해 필요한 구현체를 생성 및 제공받는다. 이러한 방식은Abstraction이Implementor와 직접적인 종속성을 갖지 않도록 한다. -
Implementor를 공유한다. -
다중 상속을 이용한다.
Abstraction클래스는public으로 상속받고,ConcreteImplementor는private로 상속받아 인터페이스와 구현을 합칠 수 있다.
Sample Code
class Window {
public:
Window(View* contents);
// requests handled by window
virtual void DrawContents();
virtual void Open();
virtual void Close();
virtual void Iconify();
virtual void Deiconify();
// requests forwarded to implementation
virtual void SetOrigin(const Points at);
virtual void SetExtent(const Point& extent);
virtual void Raise();
virtual void Lower();
virtual void DrawLine(const Point&, const Point&);
virtual void DrawRect(const Point&, const Point&);
virtual void DrawPolygon(const Point[], int n);
virtual void DrawText(const char*, const Point&);
protected:
WindowImp* GetWindowImp(); View* GetView();
private:
WindowImp* _imp;
View* _contents; // the window's contents
};
Window 클래스는 WindowImp 클래스의 인스턴스에 대한 참조자를 관리한다. WindowImp의 클래스 구조는 다음과 같으며, Window에 정의된 연산과 다른 이름의 연산들이 정의되어 있다. 이는 실제로 윈도우를 생성하고 관리하는 데 필요한 구체적 연산들이다.
class WindowImp {
public:
virtual void ImpTop() = 0;
virtual void ImpBottom() = 0;
virtual void ImpSetExtent(const Point&) = 0;
virtual void ImpSetOrigin(const Point&) = 0;
virtual void DeviceRect(Coord, Coord, Coord, Coord) = 0 ;
virtual void DeviceText(const char*, Coord, Coord) = 0;
virtual void DeviceBitmap(const char*, Coord, Coord) = 0;
// lots more functions for drawing on windows...
protected:
WindowImp();
};
Window를 상속하여 새로운 서브클래스를 정의하여 다른 종류의 윈도우를 정의할 수 있다. 이렇게 정의된 새로운 서브클래스에는 추가적인 서비스를 제공하는 연산이 추가될 수 있다.
class ApplicationWindow : public Window {
public:
// ...
virtual void DrawContents();
};
void ApplicationWindow::DrawContents() {
GetView()->DrawOn(this);
}
IconWindow는 나타낼 아이콘에 대한 비트맵을 저장하기 위해 Window를 상속해 새롭게 정의한 클래스다.
class IconWindow : public Window {
public:
// ...
virtual void DrawContents();
private:
const char* _bitmapName;
};
여기서도 WindowImp에 대한 참조자를 이용해 구체적인 구현을 얻어 자신의 서비스를 만든다.
void IconWindow::DrawContents() {
WindowImp* imp - GetWindowImp();
if (imp != 0) {
imp->DeviceBitmap(_bitmapName, 0.0, 0.0);
}
}
Window 연산들은 WindowImp 인터페이스에 따라 구현되는데, Window에 정의된 서비스 개념의 DrawRect()는 두 개의 Point 매개변수에서 네 개의 좌표를 구할 수 있다. WindowImp에 정의된 DeviceRect() 연산을 호출해 윈도우에 사각형을 그린다.
void Window::DrawRect(const Point& pi, const Point& p2) {
WindowImp* imp = GetWindowImp();
imp->DeviceRect(pl.X(), pl.Y(), p2.X(), p2.Y());
}
WindowImp를 상속받는 서브클래스들을 이용해 서로 다른 윈도우 시스템을 지원할 수 있게 하는데, XWindowImp는 X Window 시스템을 지원한다.
class XWindowImp : public WindowImp {
public:
XWindowImp();
virtual void DeviceRect(Coord, Coord, Coord, Coord);
// remainder of public interface...
private:
// lots of X window system-specific state, including:
Display* _dpy;
Drawable_winid; //windowid
GC _gc; // window graphic context
};
PM(Presentation Manager)에 대해 PMWindowImp를 정의한다.
class PMWindowImp : public WindowImp {
public:
PMWindowImp();
virtual void DeviceRect(Coord, Coord, Coord, Coord);
// remainder of public interface...
private:
// lots of PM window system-specific state, including:
HPS _hps;
};
이 서브클래스들은 윈도우 시스템의 기본 기능들을 이용해 WindowImp 연산을 구현한다. 예를 들어, DeviceRect()는 X Window 시스템에서 다음과 같이 구현할 수 있다.
void XWindowImp::DeviceRect(Coord xO, Coord yO, Coord xl, Coord yl) {
int x = round(min(xO, xl));
int y = round(min(yO, yl));
int w = round(abs(xO - xl));
int h = round(abs(yO - yl));
XDrawRectangle(_dpy, _winid, _gc, x, y, w, h);
}
PM 구현은 다음과 같다.
void PMWindowImp::DeviceRect(Coord xO, Coord yO, Coord xl, Coord yl) {
Coord left = min(xO, xl);
Coord right = max(xO, xl);
Coord bottom = min(yO, yl);
Coord top = max(yO, yl) ;
PPOINTL point[4];
point[0].x = left; point[0].y = top;
point[l].x = right; point[1].y = top;
point[2].x = right; point[2].y = bottom;
point[3].x = left; point[3].y = bottom;
if (
(GpiBeginPath(_hps, 1L) == false) ||
(GpiSetCurrentPosition(_hps, &point[3]) == false) ||
(GpiPolyLine(_hps, 4L, point) == GPI_ERROR) ||
(GpiEndPath(_hps) == false)
){
// report error
} else {
GpiStrokePath(_hps, 1L, OL);
}
}
즉 WindowImp 인터페이스의 구체적인 구현을 각 플랫폼별 시스템에 맞춰 구현할 수 있게 된다.