디자인 패턴/GoF

[디자인 패턴] 구조 패턴 (2) 가교 Bridge

로파이 2021. 1. 27. 14:50

가교 (Bridge) 패턴

 

1. 의도

  • 개체의 틀(Handle)과 내용(Body)을 분리하여 다른 클래스 계통을 가지게 함

2. 활용

예시 Window - WindowImp

가교 패턴 예시

Window 창에 대한 인터페이스를 제공하는 Window 클래스가 있고 같은 계통으로 상속할 수 있는 클래스가 있다.

1. Window 창 종류의 변화

  • Icon을 표시하는 창이거나 잠시 로그인으로 띄우는 Transient 임시 창일 수도 있다.

2. Window 창을 그리는 시스템 환경

  • Window를 X Window 시스템이나 PM Window 시스템에서 그릴 수 있다.

IconWindow이면서 X Window환경에서 그리려면 어떻게 해야할까

IconWindow를 서브 클래싱하는 IconXWindow 클래스를 정의함으로 해결할 할 수 있다.

하지만 이는 Window 종류와 시스템 확장에 따라 서브 클래스를 새롭게 계속 정의해야하고 복잡해진다는 단점이 있다.

또한 그러한 IconXWindow 클래스는 XWindow 시스템에 종속적인 구현이 되므로 바람직하지 않다.

 

따라서 종류를 표현하는 추상부와 실제 주어진 시스템에 그리는 작업을 수행하는 구현부를 나누어 클래스 계통을 달리하고 적응자처럼 내부에서 구현부 객체에 대한 참조자를 가지고 있으면 구현 메서드를 사용할 수 있을 것이다.

 

활용 경우는 다음과 같다.

  • 추상 개념과 구현 개념을 분리하고 런타임에 구현 방법을 선택하거나 내용을 변경할 수 있도록 한다.
  • 추상과 구현 계통은 독립적으로 개발되고 컴파일시 서로 영향을 주지 않는다.
  • 구현 내용은 추상 계통 클래스에서 은닉되고 추상 표현은 구현 계통 클래스에서 은닉된다.

3. 참여자

구조 패턴 - 가교

  • Abstraction: 추상적 개념에 대한 인터페이스를 제공하고 객체 구현자에 대한 참조자를 관리.
  • RefinedAbstraction: 추상적 개념에 정의된 인터페이스를 확장.
  • Implementor: 구현 클래스에 대한 인터페이스를 제공. Abstraction은 더 추상화되어 서비스 관점에서 Implementor는 Abstraction 인터페이스 내에서 작은 단위로 실행되는 연산에 대한 인터페이스를 제공.
  • ConcreteImplmentor: (예시-시스템 환경에 따라) 구체적 구현 내용을 달리하는 객체.

4. 협력 방법

  • Abstraction 클래스가 사용자 요청을 Implementor 객체에 전달

5. 결과

  • 인터페이스와 구현을 분리
  • 독립적인 확장성을 제공
  • 구현 세부사항을 은닉

6. 구현

  1) Implementor를 하나만 둔다.

  • 구현 방법이 하나일 때 Implementor를 하나만 두고 사용한다. 개념과 구현부를 분리함에 여전히 장점이 있다.

  2) 정확한 implemntor 객체를 생성한다.

  • 유동적으로 ConcreteImplmentor를 설정하는 방식
  • 삽입, 삭제, 찾기 등 연산을 위해 초기 자료구조를 연결리스트로 선택을 하였다 크기가 커지면 해시 테이블로 변경하는 예.
  • 추상 팩토리를 이용한 객체 생성을 위임
  • 매개변수를 통해 원하는 ConcreteImplmentor 결정하거나 시스템 변수에 따라 추상 팩토리가 알아서 생산해주도록 함.

  3) Implementor를 공유

  • 구현 연산만 수행하는 인스턴스는 하나만 존재해도 되기 때문에 여러 추상 계통의 인스턴스가 참조 카운트와 같은 방식으로 공유하도록 한다. 

  4) 다중 상속을 이용

  • 적응자의 예처럼 Abstraction 클래스를 public으로 Implementor를 private으로 상속한다. 다중 상속시 결국 두 인터페이스를 만족하는 구현을 Abstraction의 서브 클래스로 구현하기 때문에 특정 구현부에 종속적일 수 밖에 없다.

예시 코드

Window와 WindowImp 예시

Window.h

서비스 관점에서 제공하는 인터페이스를 정의하고 내부적으로는 Window* 인스턴스의 구현 연산을 이용한다.

Window*에 인스턴스에 접근할 때 객체가 생성되지 않았다면 추상 팩토리를 이용하여 생성하는 것이 특징.

class Window 
{
public:
	Window(View* contents);

	// Window가 직접 처리하는 메서드
	virtual void DrawContents();
	virtual void Open();
	virtual void Close();
	virtual void Iconfiy();
	virtual void Deiconfiy();

	// 구현 클래스에 위임하는 메서드
	virtual void SetOrigin(const Point& 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()
	{
		// 추상 팩토리를 이용한 WindowImp 생산
		if (_imp == 0)
		{
			// 추상 팩토리는 단일체로 구현됨
			_imp = WindowSystemFactory::Instance()->MakeWindowImp();
		}
		return _imp;
	}
	View* GetView();
private:
	WindowImp* _imp;
	View* _contents;
};

Window를 상속하여 여러 Window 객체를 표현할 수 있다.

DrawRect() 연산은 시스템 환경에 맞는 WindowImp의 DeviceRect() 연산을 이용하여 나타낸다.

class IconWindow : public Window {
public:
	// ...
	// WindowImp 구현 메서드, DeviceXXX()를 직접 사용
	virtual void DrawRect(const Point& p1, const Point& p2)
	{
		WindowImp* imp = GetWindowImp();
		imp->DeviceRect(p1.X(), p1.Y(), p2.X(), p2.Y());
	}
	virtual void DrawContents()
	{
		WindowImp* imp = GetWindowImp();
		if (imp != 0)
		{
			imp->DeviceBitMap(_bitmapName, 0.0, 0.0);
		}
	}
private:
	// IconWindow의 고유 비트맵 이름
	const char* _bitmapName;
};

WindowImp.h

실제 구현 연산이 정의된 인터페이스. 

class WindowImp
{
public:
	// Window가 주어진 플랫폼 환경에서 동작하기 위한 실제 구현 함수
	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;
protected:
	WindowImp();
};

WindowImp를 상속받아 X 윈도우 시스템을 지원하는 구현부 예시이다.

class XWindowImp : public WindowImp
{
public:
	XWindowImp();
	// ... 
	virtual void DeviceRect(Coord x0, Coord x1, Coord y0, Coord y1)
	{
		int x = round(min(x0, x1));
		int y = round(min(y0, y1));
		int w = round(abs(x0 - x1));
		int h = round(abs(y0 - y1));
		XDrawRectangle(_dpy, _windid, _gc, x, y, w, h);
	}
	void XDrawRectangle(Display*, Drawable, GC, Coord, Coord, Coord, Coord);
private:
	// X 윈도우 플랫폼에 관한 고유 상태
	Display* _dpy;
	Drawable _windid;
	GC _gc;
};

 

-적응자 패턴과 가교 패턴 

적응자 패턴서로 관련이 없는 클래스들이 함께 동작하도록 만드는 방법이고 설계가 끝난 후 보완 단계에서 두 기능이 필요할 때 추가적으로 적용한다.

가교 패턴설계 초기부터 추상 표현과 구현부를 달리 하는 것으로 결정하고 독립적으로 개발할 수 있도록 사용한다.

 

참고 자료: GoF 디자인 패턴