DesignPatternSmalltalkCompanion:Bridge
BRIDGE(DP 151)
의도
추상화와 구현을 분리하여 각각을 독립적으로 변형할 수 있게 한다.
구조
논의
모두들 구현과 인터페이스 분리의 중요성에 대해 이야기한다. 이는 [디자인 패턴]편의 서론 (DP 11-18) 부분에서 다루는 주요 주제 중 하나이다. 객체 수준에서 보면 인터페이스를 타입으로 선언한 후 다시 클래스로 구현하는 것을 의미한다. 단일 타입을 여러 방법으로 구현하면 여러 개의 클래스가 그 단일 타입을 구현한다. 하지만 특정 객체의 경우 그의 클래스는 인터페이스와 구현을 바인딩한다.
Bridge 패턴은 인터페이스와 그의 구현을 두 개의 협력 객체로 나눈다. 두 객체가 하나의 논리 객체의 역할을 함과 동시 인터페이스와 구현이 통합되어 있다. 두 개의 객체는 두 개의 구분된 클래스의 인스턴스들이다: 추상화 개념(Abstraction)과 객체 구현부(Implementor)이다. 추상화 개념은 타입ㅡ논리 객체가 클라이언트에게 보여주는 인터페이스ㅡ을 의미한다. 그러나 추상화 개념은 그의 인터페이스를 구현하지 않는다; 구현에게 상속한다 ([디자인 패턴]편에서는 이를 “imp” 관계라고 부른다). 구현부는 실제로 추상화 개념이 제공하는 인터페이스를 구현한다.
Abstraction과 Implementor 클래스는 주로 계층구조로 구성된다. 다양한 RefinedAbstraction 들은 좀 더 일반적인 Abstraction 타입의 서브타입을 나타낸다. 다양한 ConcreteImplementor 들은 Implementor의 구현에 대한 대안 접근법들을 나타낸다. Bridge 패턴의 주요 특징은 어떠한 Abstraction이든 원하는 Implementor와 함께 작용할 수 있다는 점이다. 따라서 두 계층구조 간 관계는ㅡ서로 협력하기 위해 사용할 인터페이스ㅡ두 클래스의 계층구조의 맨 꼭대기에서 정의된다. 서브클래스가 이 인터페이스를 변경하거나 확장할 경우 한쪽 계층구조의 클래스가 나머지 계층구조의 클래스와 협력할 능력을 상실하기 때문에 인터페이스를 변경하거나 확장해서는 안 된다. 따라서 Implementor 계층구조의 모든 클래스는 동일한 인터페이스를 가져야 어떠한 Abstraction 클래스와도 협력이 가능할 것이다. Implementor가 다시 Abstraction으로 위임할 경우, Abstraction의 모든 클래스는 Implementor 들이 사용할 수 있는 단일 특권 인터페이스를 제공할 필요가 있다. 하지만 Abstraction 서브클래스는 서브타입을 나타내기 때문에 그들의 public 인터페이스들은 Abstraction 상위클래스가 선언한 기본 클래스 하나를 확장할 수 있다.
왜 브리지 패턴을 이용하는가?
객체를 왜 인터페이스와 구현부로 나눌까? 언젠가 하나의 논리 객체를 두 개 또는 그 이상의 구분된 클래스로 나눈 적이 있을 것인데 이것이 바로 그 답이 된다: 부분들이 독립적으로 작동할 수 있기 때문이다. 각 계층구조는 속성을 나타내며, 이것이 바로 다른 계층구조로부터 독립적으로 구분할 수 있게 해준다. Bridge 패턴에서는 구분된 두 개의 속성이 인터페이스와 구현이 된다. 하나의 계층구조에서 두 개의 직교(독립적) 속성을 구현하려 할 경우 코드중복으로 이끌 수밖에 없다.
좀 더 구체적 예제를 살펴보려면 [디자인 패턴] (DP 151)에 실린 단일 윈도우 클래스 계층구조의 다이어그램을 살펴보라.
Implementation1OfRefinedAbstractionΑ 의 코드는 Implementation1OfRefinedAbstractionB의 코드와 매우 흡사할 수 밖에 없다. 두 클래스의 실질적 차이는 그들이 위치한 장소이다. 이상적으로 보면 “Implementation 1”은 RefinedAbstractionA와 RefinedAbstractionB 모두가 재사용할 수 있는 한 장소에만 부호화되어 있어야 한다.
클래스 상속(서브클래싱)을 대신할 수 있는 유연한 접근법이 필요하다는 문제를 해결하는 것이 Decorator (161) 패턴의 주요 동기이다. 이 패턴은 일반 Component 클래스를 두 개의 하위계층구조로 나눈다: 감싸지는 다양한 ConcreteComponent 들과 컴포넌트를 감싸는 다양한 Decorator들로 나뉜다. Decorator 접근법의 한계점은, 감싸진 컴포넌트와 그렇지 않은 컴포넌트 간 차이를 클라이언트가 쉽게 볼 수 있어서 Decorator와 ConcreteComponent가 동일한 Component 인터페이스를 공유해야 할 뿐더러 둘 중 어느 것도 인터페이스를 확장할 수 없다는 점이다. Bridge 패턴에서는 단일 인터페이스를 구현하는 이러한 대안적 방법들을 완전히 구분된 계층구조로 추출하여 첫 번째 계층구조에서 완전히 다른 인터페이스를 가질 수 있다. Implementor들은 모두 동일한 인터페이스를 가져야 하는 반면 Abstraction 서브클래스들은 Abstraction 인터페이스를 확장할 수 있다.
IBM 스몰토크 집합체
IBM 스몰토크의 초기 버전 중에는 LaLonde와 Pugh (1995)이 문서화한 Bridge 패턴의 모델 예제도 포함된다. 이 버전들은 한 집합체를 두 개의 부분으로 구현한다: 하나는 어떻게 작동하는지를 설명하는 인터페이스부와 나머지 하나는 가능한 효율적으로 작동하게 만드는 구현부이다. 예를 들어 아래 다이어그램은 Set를 구현하는 클래스들을 보여준다. 집합규모가 작으면 그 요소들은 선형목록 EsLinearSet에 저장된다. 큰 규모라면 해시 테이블 EsLinearHashSet에 요소가 저장된다. 이러한 배열은 두 경우 모두 가능한 빨리 검색할 수 있도록 해준다. EsLinearSet로 소규모 집합이 늘어날 경우 Set는 요소가 많아지면서 EsLinearSet로 구현을 변경한다.
이는 Bridge 패턴의 예제이다. Set 계층구조는 Abstration이 되고, Implementor는 EsImplementationSet 계층구조가 된다. EsImplementationSet은 그것의 Set를 구현한다. Set 자체는 요소를 포함하지 않는다; 그 EsImplementationSet를 사용해 요소를 포함시킨다. EsimplementationSet는 클라이언트에 의해 숨겨져 있다; 그리고 이것은 Set에 관한 정보만 알고 있다.
기존 IBM Collection 계층구조는 Bridge 패턴의 다른 예제를 제공한다. OrderedCollection과 SortedCollection은 EsOrderedCollectionImplementor와 EsSortedCollectionImplementor의 서브클래스를 사용해 추상화 개념을 구현하는 AdditiveSequenceableCollection의 서브클래스에 해당한다. 이와 비슷하게 Dictionary 또한 적절한 EsImplementationSet를 사용해 그의 연관(association)을 저장한다.
Collection 계층구조는 IBM 스몰토크 3.0 버전부터 Bridge 패턴의 사용을 중지하였다. 복잡성이 늘어난 만큼 효율성이 따라주지 못한 것은 확실하다. 개발자는 Set, EsIdentitySet, EsSymbolSet, EsLargeSymbolSet 중 선택할 수 있지만 이 집합체들은 이제 통일된 방식으로 요소를 저장한다. 처음 3개의 클래스들은 소규모 집합체용으로서 저장에 Array를 사용하는 반면 EsLargeSymbolSet는 EsSymbolSet들의 Array를 사용한다.
윈도우 계층구조
[디자인 패턴] 편의 동기 부분에서는 Bridge 패턴의 전형적 예를 제공한다. 여기는 윈도우를 두 개의 부분으로 구현하는 방법을 설명한다. 하나는 윈도우의 종류로, 일반, 상징적, 또는 일시적을 의미한다. 또 다른 하나는 윈도우를 생성하는 윈도우 시스템(룩앤필)이며 X-Widows, Presentation Manager 등을 포함한다. 각각의 슈퍼클래스인 Window와 WindowImplmentation ([디자인 패턴] 편에서는 “WindowImp”라고 부름) 에 공통 인터페이스가 정의되어 있으므로 어떤 윈도우 타입이든 원하는 윈도우 시스템과 함께 작동할 수 있다. 이 공통 인터페이스는 아래 다이어그램이 나타내는 것과 같이 Bridge 패턴의 Bridge가 된다.
위젯을 윈도우 시스템으로부터 구분할 경우 여러 룩앤필 표준을 모방할 수 있으므로 비주얼웍스에서 사용하기에 완벽한 것처럼 보인다; 그러나 비주얼웍스는 이러한 역할을 수행하는 데에 있어 굳이 Bridge를 필요로 하지 않는다. 각 위젯은 사실상 클래스에 그치는 것이 아니라 하나의 계층구조가 된다. 슈퍼클래스가 위젯의 인터페이스를 정의하고 서브클래스는 서로 다른 룩앤필을 위한 인터페이스를 구현한다. 예를 들어, CheckButtonViewㅡ체크박스 클래스ㅡ는 Win3CheckButtonView, MacCheckButtonView, MotifCheckButtonView와 같은 서브클래스를 가진다. 이것은 Bridge가 아니라 특수화된 서브클래스들의 계층구조이다. IBM 스몰토크는 여러 플랫폼에 걸쳐 호환되는 것과 비슷하며, 서로 다른 자체 위젯 집합들을 이용할 수 있지만 Bridge로서 구현하지는 않는다.
위젯-구현 브리지
Widget_implementation Bridge
비주얼웍스 2.5 버전에서는 원하는 룩앤필을 모방할 수 있는 위젯을 구현하는 데 Bridge을 사용하는 것은 아니지만 비주얼웍스의 제안된 버전은 Bridge를 이용해 위젯을 구현하고자 시도한다. Yelland는 모방 대신 자체 플랫폼 위젯을 사용하면서 플랫폼의 호환성을 유지하는 비주얼웍스를 위해 제안된 아키텍쳐를 설명하였다 (Yelland, 1996; Yelland의 논문에 대한 논의는 Flyweight (189)를 참조). 위젯 클래스의 계층구조는 나머지 스몰토크 시스템에 (예: Widget의 서브클래스인 RadioButtonWidget와 ListWidget) 대한 위젯의 인터페이스를 정의한다. 그리고 지원하는 각 플랫폼을 위해 (Win32Bridge 계층구조에 포함된 Win32RadioButtonBridge, Win32ListBridge 등) 위젯 구현의 계층구조를 포함시키기도 한다. 따라서 비주얼웍스의 모든 위젯 클래스는 공통된 슈퍼클래스로부터 상속되는 반면 특정 플랫폼을 위한 모든 Bridge 클래스들은 그 플랫폼에 위치한 모든 위젯에 공통되는 행위를 상속한다.
이 인스턴스 다이어그램은 매킨토시에 반해 윈도우 머신에서 라디오 버튼이 구성되는 방법을 보여준다.
플랫폼 중립 위젯과 플랫폼 특정적 위젯을 구분하게 되면 스몰토크 이미지 스스로 플랫폼 중립 형태로 저장할 수 있도록 해준다. 저장과정은 플랫폼 특정적 객체를 보존하지 않는다. 이미지가 다시 실행되어 플랫폼 중립 위젯을 재활성화시키면 그들은 최근 플랫폼에 적절한 플랫폼 특정적 위젯을 생성한다.
Yelland는 위젯을 Widget과 WidgetBridge 계층구조로 구분하는 것을 Bridge 패턴의 예제로 간주하지만 실제로는 Widget을 WidgetProxy에 조절하는 Adapter (105) 패턴의 예제에 가깝다. 서로 다른 플랫폼에 대한 프록시는 서로 다른 인터페이스를 갖지만, Widget 클래스는 여전히 모든 구현이 동일한 인터페이스를 가질 것으로 예상하여 각 WidgetProxy 클래스는 그에 상응하는 WidgetBridge 클래스ㅡWidget 클래스가 예상하는 대로 표준 인터페이스에 맞춰 조절하는 기능을 한다ㅡ를 가질 것으로 예상한다.
프레임워크는 Widget을 그의 WidgetBridge로 매핑하기 위해 추상 팩토리 (31) WidgetPolicy도 포함한다. 진정한 Bridge 패턴을 사용하는 예제라면 Implementor 클래스를 찾기 위해 추상 팩토리를 사용하지 않을 것이다.
이 프레임워크는 인터페이스를 (Widget들) 구현부로부터 (WidgetProxy들) 분리시키기 때문에 Bridge 패턴의 진정한 목적을 달성하고는 있다. 하지만 “Bridge” 객체를 (WidgetBridge) 인터페이스와 구현 객체들 간에 삽입하므로 프레임워크가 Bridge 패턴에 따라 구현되었다고 할 수 없다.
가설적 예제
Hypothetical Example
그렇다면 스몰토크에서 어떻게 Bridge 패턴을 사용할 수 있을까? 여러 종류의 노드(node)가 있는 트리를 필요로 하는 시스템을 가정해보자. 트리의 일부 노드는 편집이 가능하다; 그 외의 노드는 그렇지 않다. 우선은 트리 구조이기 때문에 일부 노드는 Composite (137) 이고, 나머지는 Leave이다. 그렇다면 문제는 4가지 결합ㅡ편집 가능 vs. 편집 불가, 복합객체 vs. 잎(leaf)ㅡ을 모두 지원하는 계층구조를 어떻게 구현할 것인가가 된다. 이에 대한 정답은 두 개의 계층구조를 사용하는 것이다: 하나는 편집 가능 속성, 또 하나는 복합 계층구조를 이용하는 것이다. 편집 가능한 클래스들은 복합 계층구조의 클래스에 의해 구현되는 노드에 적용되는 인터페이스를 제공한다. 다음 다이어그램은 정확한 Bridge 패턴을 보여주는 훌륭한 예제이다:
이 설계는 우리가 작업한 애플리케이션용으로 고려되었지만 애플리케이션에서 필요로 하는 것보다 복잡한 것으로 밝혀져 결국은 거절되었다.
거의 사용되지 않은 패턴
스몰토크에 사용된 Bridge 패턴의 예제가 몇 가지 있다. 잠재적으로는 아주 유용한 패턴이 있다거나 사용된 적이 많은 것은 아니다.. [디자인 패턴] 편에서는 다른 언어에서 사용된 예를 몇 가지 소개하고 있지만 스몰토크는 포함되지 않았다 (DP 160). 스몰토크에서의 가장 괜찮은 사용예는 IBM 스몰토크 초기 버전에서 집합체를 구현한 방식인데, 이후 버전에서 제거되었다. 다른 언어들은 Bridge 패턴을 이용해 집합체를 구현하지만ㅡ[디자인 패턴] 편에 C++에서 사용되는 libg++를 언급ㅡ스몰토크는 Bridge가 꼭 필요하다고 보지 않는다. 플랫폼 이동이 가능한 (platform-portable) 스몰토크 방언들은 이를 이용하여 [디자인 패턴]편 동기 부분에서 제안하는 방식대로 다양한 GUI 룩앤필을 구현할 수는 있으나 사용하지는 않는다. 자체 위젯을 위해 제안된 비주얼웍스 아키텍쳐는 디자인 Bridge의 일부만 호출하지만 그 구현은 추상 팩토리 (31)의 도움을 받은 Adapter (105) 패턴으로 이루어진다. NeXT의 AppKit는 Bridge 패턴을 이용해 장치 독립적인 그래픽 이미지를 구현하나, 스몰토크 이미지 클래스들은 (비주얼웍스에 Image 계층구조와 같은) Bridge 를 사용하지 않고 플랫폼 독립성을 이룬다. Handle/body는 Bridge와 유사하면서도 너무 전문화되어 있어 언어중립적 패턴이 아니라 C++ idiom이라 할 수 있다.
협력하는 계층구조
스몰토크에서 Bridge 패턴을 사용하는 예는 드물지만 그 뒤에 숨은 원칙들은 우리가 협력하는 계층구조(Collaborating Hierarchies)라고 부르는 광범위한 범위에 매우 활용적이다. 객체가 객체 상속을 통해 특수화될 수 있는 속성이 하나 이상인 경우 이 속성들은 서로에게 직교되어 있으며, 협력하는 계층구조들은 서브클래스가 다른 서브클래스와 독립되어 다르게 나타날 수 있는 고유의 계층구조로 각 속성을 분리시킨다. 이 계층구조들 간 관계와 인터페이스는 계층구조의 꼭대기에 위치한 클래스에서 정의되며, 한 계층구조의 어떤 서브클래스든 다른 계층구조의 어떠한 서브클래스와도 함께 작동할 수 있다. 클라이언트 객체는 두 계층구조 중 원하는 것과 협력할 수 있다. 일반적으로는 한 클라이언트가 한 계층구조와 협력하고 다른 클라이언트는 나머지 계층구조와 협력한다.
이야기만 들으면 Bridge 패턴처럼 들리는데, Bridge 패턴은 사실 협력하는 계층구조들의 전문화 패턴이다. Bridge 패턴의 경우 서로 독립적으로 다를 필요가 있는 두 속성은 항시 인터페이스와 (Abstraction) 구현부(Implementor)가 된다. 클라이언트는 항상 Abstraction과만 협력하여 Implementor가 클라이언트에 의해 숨겨지도록 한다. Implementor 클래스는 클라이언트를 위한 public 인터페이스는 갖고 있지 않고 Abstraction을 위한 특권 인터페이스만 가진다.
협력하는 계층구조의 예제
협력하는 계층구조들은 함께 작용하는 객체 쌍들의 소스이다. 비주얼웍스에서 구현되는 ViewController가 좋은 예가 된다. View와 Controller는 서로를 필요로 한다. 다음 페이지의 다이어그램이 텍스트 위젯에 나타나는 이 관계를 보여준다:
View-Controller 쌍을 생성하기 위해 클라이언트는 View와 Controller 계층구조 각각에 인스턴스를 생성시켜 함께 놓는다. 클래스 결합이 많으면 유용한 행위를 생성하지 못하지만 많은 사례에서 잘 작동하고 있다. 두 계층구조는 기존 클래스와 새 클래스로부터 새로운 결합을 생성하기 위해 독립적으로 확장될 수 있다.
협력하는 계층구조에서 더 유연하지만 순수한 형태는 아닌 협력 계층구조가 있는데, 비주얼웍스에 사용되는 ValueModel과 값 기반의 하위뷰 클래스가 그것이다. 값 기반의 하위뷰는 (예: 체크박스나 입력필드와 같은 위젯) 그 모델에 대한 ValueModel를 기대한다. ValueModel의 값이 정확한 타입이라는 조건에 한해 어떠한 ValueModel이든 작동할 것이다. 값 기반의 하위뷰 클래스는 모두 원하는 ValueModel 서브클래스를 사용할 수 있기 때문에 수 많은 결합이 가능하다. ValueModel이라는 단일 계층구조가 있지만 “ValueBasedSubview” 계층구조는 존재하지 않는다; 값 기반의 하위뷰 클래스는 View 계층구조에 흩어져 존재한다 (Woolf, 1995a).
협력하는 계층구조에 대한 예제는 있지만 Bridge 패턴의 예제는 될 수 없다. 인터페이스와 구현 계층구조로 구성된 협력 계층구조는 거의 없는 것이 사실이다. View-Controller는 책임성의 구분을 의미한다. 두 계층구조 모두 서로를 위한 인터페이스라고 말할 수 있지만 어느 쪽도 서로의 구현이 될 순 없다. 게다가 둘 다 서로를 포함하지 않는다. 클라이언트는 둘 중 하나로 접근하여 협력하면서 나머지 하나로부터는 독립적일 수 있다. View와 Controller는 Bridge 가 아니라 상호 Strategy (339) 에 속한다. ValueModel과 값 기반의 하위뷰들도 마찬가지이다.
협력하는 계층구조의 또 다른 예는 비주얼웍스의 Geometric과 GeometricWrapper 간 관계이다. GeometricWrapper는 비주얼 데코레이터 계층구조의 부분이지만 Geometric을 감싸진 않는다; 단지 조정할 뿐이다. (이 클래스들에 관한 다이어그램은 예제 코드 단락을 참조한다.) Geometric 계층구조는 기하정보로부터 Circle, Rectangle, LineSegment와 같은 공통된 2차원 도형을 구현한다. 시각 자료로 구현되는 것은 아니지만 GeometricWrapper를 이용해 시각적으로 표시할 수는 있다. GeometricWrapper는 Geometric을 감싸 이에 라인 굵기와 같은 속성과 비주얼 인터페이스를 제공한다. StrokingWrapper는 Geometric의 윤곽선을 그리고, FillingWrapper는 닫혀진 Geometric 모양을 채워 그린다. GeometricWrapper는 비주얼 데코레이터의 루트에 해당하는 래퍼의 서브클래스로서 구현된다. 따라서 GeometricWrapper는 Decorator (161) 패턴으로 보이기도 한다. 하지만 Geometric은 비주얼이 아닐 뿐더러 GeometricWrapper는 비주얼이기 때문에 GeometricWrapper는 사실상 Decorator 패턴이 될 수 없으며, 이에 따라 Wrapper 계층구조에 구현되어선 안 된다. GeometricWrapper는 Geometric의 수학적 인터페이스를 비주얼 인터페이스로 변환하기 때문에 사실상 Adapter (105) 패턴이라 할 수 있으므로 leaf 비주얼 계층구조에 구현되어야 한다.
그렇다면 GeometricWrapper는 단순히 Adapter라고 할 수 있을까? Geometric과 GeometricWrapper 둘 다 계층구조이다. 첫 번째는 서로 다른 모양을 나타내고, 후자는 이를 그리는 방법들을 생성한다. 닫힌 모양을 채우기 또는 스트로크로 그릴 수도 있다. 따라서 하나의 계층구조에서 모양을 하나 선택하고 다른 계층구조로부터 그리기 방식을 선택한 후 둘을 합치면 기하학 시각 자료가 만들어진다. 이는 협력하는 계층구조로서, 둘 중 하나는 Adapter의 계층구조이다.
비주얼웍스의 Window와 WindowDisplayPolicy 간 관계를 또 다른 예로 들 수가 있겠다. 두 계층구조 중 하나가 나머지 한 계층구조의 Strategy (339) 계층구조가 되는, 협력 계층구조의 예이다.
대부분 Window들은 사실상 재표시하기를 수행하는 WindowDisplayPolicy를 구분하기 위해 디스플레이 행위를 위임한다. (TransientWindow는 위임하는 대신 WindowDisplayPolicy에 코드를 복제하므로 예외가 된다.) WindowDisplayPolicy의 서브클래스인 DoubleBufferedWindowDisplayPolicy 또한 그 윈도우를 재표시하지만 깜빡거림을(flicker) 감소시기 위해 더 많은 메모리를 소모하는 이중 버퍼 그래픽을 사용한다. 따라서 디스플레이 정책 클래스는 Strategy (339) 객체로서 윈도우가 어떻게 스스로 재표시할 것인지 정확히 결정한다. 하지만 단순히 Strategy라기보다는 협력하는 계층구조라고 보는 것이 옳다. ScheduledWindow와 WindowDisplayPolicy 모두 규모가 작긴 하지만 계층구조들로서, 어떤 윈도우든 원하는 디스플레이 정책과 함께 작동할 수 있다.
구현
[디자인 패턴]편에 제기된 구현 문제에 더해 여기서는 Bridge와 협력하는 계층구조에 모두 적용되는 두 가지 문제를 고려해보고자 한다:
- Abstraction과 Implementor 계층구조. [디자인 패턴] 편에서는 Bridge가 두 개의 계층구조에 구현된다고 명시하고 있다. C++의 경우 정적 타이핑(static typing)을 쓰기 때문에 이것이 필요하다. 스몰토크의 경우 동일한 타입의 클래스들이라고 해서 동일한 계층구조에 구현될 필요는 없다. 대부분 패턴에서는 동일한 타입의 클래스를 동일한 계층구조에 구현하는 것을 직접 요구하지는 않지만 바람직하다고 간주한다. Bridge 또는 협력하는 계층구조의 경우 두 클래스 집합은 두 계층구조에 구현되어야 하며, 상위 추상 클래스들 간 관계가 명시되어야 한다; 그렇지 않을 경우 패턴을 거의 알아보기가 불가능하며, 사용 및 관리가 훨씬 줄어든다.
- 강력한 커플링. LaLonde와 Pugh (1995)는 Abstraction과 Implementor가 정말로 강하게 결합되어 있는지에 대한 문제를 제기하였다; 다시 말해, Abstraction이 Implementor를 참조하는데 그치는 것이 아니라 Implementor 또한 그것의 Abstraction를 참조해야 하는가하는 질문이다. 이는 Implementor가 다른 Implementor로 교체해줄 것을 Abstraction에게 알려주도록 해준다. 예를 들어, IBM 스몰토크에 사용되는 Set Bridge는 강력하게 커플링되어 있다.
예제 코드
스몰토크에 진정한 Bridge 패턴이 구현된 예제가 거의 없기 때문에 코드 예제를 소개하기가 까다로웠다. 대신 비주얼웍스의 Geometric/GeometricWrapper 협력하는 계층구조에 관한 코드를 살펴보겠다. 이 프레임워크의 구조는 다음과 같다:
Geometric 계층구조는 Retangle, Circle, LineSegment, Polyline과 같은 서브클래스로 기하학적 디자인의 추상화개념을 모형화한다. Rectangle과 LineSegment를 대표로 살펴보자:
Object subclass: #Geometric
instanceVariableNames: ''
classVariableNames: '...'
poolDictionaries: ''
Geometric subclass: #Rectangle
instanceVariableNames: 'origin corner'
classVariableNames: ''
poolDictionaries: ''
Geometric subclass: #LineSegment
instanceVariableNames: 'start end'
classVariableNames: ''
poolDictionaries: ''
Geometric의 기본 특징은 그것이 직사각 영역을 가로지르는지 말해준다는 점이다. 교차는 두 가지 방법으로 이루어진다: 윤곽선이 영역과 마주칠 수 있고, 그 지역(region)이 (채워진 영역) 영역을 가로지를 수도 있다. (윤곽선이 겹치지 않는다고 해서 지역이 영역을 가로지를 수 없는 것은 아니다. 닫힌 기하학 무늬에 테스트 직사각형이 포함되어 있을 경우 그 윤곽선은 교차하지 않지만 지역은 교차한다.) 닫힌 기하학적 디자인만 그 지역을 알기 때문에 Geometric은 기하학적 패턴이 닫혔는지 결정하기 위해 canBeFilled를 구현한다:
Geometric>>canBeFilled
"Is this geometric closed?"
^flase
Rectangle>>canBeFilled
^true
LineSegment는 false의 Geometric 구현을 상속하기 때문에 canBeFilled를 구현하지 않는다. (이는 구체적 오퍼레이션의 예제이다; 템플릿 메소드를 (355) 참조한다.)
기하학적 디자인은 유용한 모형이지만 수학적 추상 개념으로서, 실제로 어떤 모양과도 닮지 않았다. 기하학적 디자인은 숫자나 점이 아니듯 시각 자료도 아니므로 VisualComponent 계층구조에 구현되지 않는다. 하지만 모양과 같이 윈도우에 시각적으로 표시하고 싶을 때가 있다. 이럴 때 문제는 시각 자료가 아닌 객체를 시각 자료처럼 나타내려면 어떻게 해야 하는가이다.
이것이 바로 비주얼웍스가 GeometricWrapper 계층구조를 구현하는 이유이다. Geometric은 시각 자료가 아니지만 GeometricWrapper는 시각 자료가 맞으며 그들은 그 모양을 어떻게 그릴지 결정하기 위해서 Geometric과 어떠한 상호작용을 해야 하는지를 알고 있다. (비주얼 트리가 어떻게 작용하는지에 대한 토론은 Composite (137)을 참조한다. Wrapper 비주얼의 역할에 관한 토론은 Decorator (161)를 참조한다.
기하학적 디자인을 표시하는 방법에는 두 가지가 있다: 윤곽선 또는 지역으로 나타내는 방법이다 (닫힌 기하학적 디자인 또한 지역이 있다.) 따라서 GeometricWrapper의 종류는 두 가지가 있다: 지역을 표시하는 FillingWrapper와 윤곽선을 표시하는 StrokingWrapper가 있다. Geometric는 실제로 래퍼의 component로서 저장된다.
VisualPart subclass: #Wrapper
instanceVariableNames: 'component'
classVariableNames: ''
poolDictionaries: ''
Wrapper subclass: #GeometricWrapper
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
GeometricWrapper subclass: #FillingWrapper
instanceVariableNaems: ''
classVariableNames: ''
poolDictionaries: ''
GeometricWrapper subclass: #StrokingWrapper
instanceVariableNaems: 'lineWidth'
classVariableNames: ''
poolDictionaries: ''
모든 시각적 구현들은 스스로를 표시하기 위해 displayOn: 메시지를 구현하며 GeometricWrapper도 예외가 아니다. (객체지향 순환의 논의와 displayOn: 과 같은 메시지가 어떻게 비주얼 트리를 순환하는지는 Chain of Responsibility (225)를 참조한다.) Wrapper는 그의 component를 표시함으로써 스스로를 표시하므로, GeometricWrapper는 그의 Geometric을 표시함으로써 스스로를 표시한다. 그러나 GeometricWrapper는 그것의 Geometric 윤곽선이나 지역의 표시 여부를 알지 못한다. 따라서 displayOn:의 구현은 그 서브클래스로 미루어야 한다. (이는 템플릿 메소드에서의 (355) 추상 메소드이다.) 반면 서브클래스는 윤곽선이나 지역의 표시 여부를 알기 때문에 Geometric에게 실행 여부를 알린다.
GeometricWrapper>>displayOn: aGraphicsContext
"Display this object on aGraphicsContext."
self subclassResponsibility
FillingWrapper>>displayOn: aGraphicsContext
component displayFilledOn: aGraphicsContext
StrokingWrapper>>displayOn: aGraphicsContext
lineWidth == nil
ifFalse: [aGraphicsContext lineWidth: lineWidth].
component displayStrokedOn: aGraphicsContext
각 Geometric은 채우기 (영역) 또는 스트로크로 (그것의 윤곽선) 그림 그리기를 책임진다. Rectangle은 둘 다 가능하지만 LineSegment는 닫혀 있으므로 스스로를 스트로크로 그리는 수 밖에 없다. 도형은 GraphicsContext를 사용하여 스스로를 그린다:
Geometric>>displayFilledOn: aGraphicsContext
"Use aGraphicsContext to draw this shape's region."
self subclassResponsibility
Geometric>>displayStrokedOn: aGraphicsContext
"Use aGraphicsContext to draw this shape's outline."
self subclassResponsibility
Rectangle>>displayFilledOn: aGraphicsContext
aGraphicsContext displayRectangle: self
Rectangle>>displayStrokedOn: aGraphicsContext
aGraphicsContext displayRectangularBorder: self
LineSegment>>displayFilledOn: aGraphicsContext
self shouldNotImplement
Line Segment>>displayStrokedOn: aGraphicsContext
aGraphicsContext displayLineFrom: start to: end
어떤 GeometricWrapper를 사용하든 닫힌 Geometric을 표시할 수 있다. 열린 Geometric도 윤곽이 있는 래퍼와 함께 작용한다. GeometricWrapper는 지역 또는 윤곽선으로 그려진 도형의 시각적 추상화를 제공하며, Geometric은 도형을 그리는데 필요한 메시지와 관련해 도형의 외형을 구현한다. GeometricWrapper는 나머지 시스템에게서 Geometric를 숨긴다.
기하학적 디자인을 윈도우에 표시하는 방법의 예를 들기 위해 Geometric class>>twoSquares라는 메소드를 살펴보겠다:
Geometric class>>twoSquares
| g1 g2 |
...
g1 := (0 @ 0 extent: 100 @ 100) asFiller.
g2 := (100 @ 100 extent: 100 @ 100) asStroker.
...
이 코드는 Rectangle을 생성하여 이곳으로 asFiller를 전송한다. Rectangle은 자체에 FillingWrapper를 생성시켜 래퍼를 리턴한다 (Geometric이 닫혀있지 않을 경우 asFiller는 실패할 것이다). 이와 비슷한 방식으로, g2는 또 다른 Rectangle에 StrokingWrapper를 생성시킨다. 이 두 개의 래퍼가 윈도우에 추가되면 표시할 수 있다.
알려진 스몰토크 사용예
Bridge는 스몰토크에서 매우 드물게 사용된다; 객체를 인터페이스와 구현 계층구조로 구분할 필요가 거의 없다. 이로 인해 증가하는 복잡성이 이익보다 크기 때문이다.
협력하는 계층구조는 스몰토크에서 매우 흔하게 사용된다. 유일한 논리 객체가 주로 2개 또는 그 이상의 계층구조로 나뉘며, 각 계층구조는 단일 속성을 허용하여 서브클래싱을 통해 다른 계층구조들과 독립되어 서로 다르게 존재한다. 각 계층구조는 인터페이스나 구현뿐만 아니라 필요한 속성은 무엇이든 재현할 수 있다.
Bridge
IBM 스몰토크의 초기 버전에서는 여러 개의 Collection 하위계층구조가 하나의 Bridge로 구현되곤 했다.
비주얼웍스의 Button 클래스는 Bridge 패턴의 가능성이 있는 예제라고 언급되었지만 사실 아니다. Button은 Wrapper 계층구조의 멤버(member)로서 LabeledButtonViewㅡ체크상자, 푸쉬, 라디오 버튼용 플랫폼 특정적 버튼 구현ㅡ을 포함한다. 버튼이 추상화 개념의 역할을 하고 LabeledButtonView는 구현부와 같은 역할을 하기 때문에 언뜻 보면 Bridge 패턴처럼 보인다. 하지만 Button은 추상화 개념과 같은 계층구조가 아니다. Button은 플랫폼 중립적 정보(예: 어떤 종류의 버튼인지, 그 라벨은 무엇인지)를 저장하는 Decorator (161)에 속한다.
이와 비슷하게 비주얼웍스의 SpecWrapper 또한 Bridge로 설명되지만 그렇지 않다. Wrapper 계층구조의 멤버인 SpecWrapper는 위젯의 추상적 설명과 그를 구현하는 객체를 묶는다. SpecWrapper는 다른 룩앤필로 변경하기 위해 위젯을 내던지고, 새 위젯을 생성하기 위해 새로운 룩앤필 설정과 사양(spec)을 사용한다. 다소 드문 경우이긴 하지만 Decorator (161) 패턴의 예제이다.
협력하는 계층구조
비주얼웍스에 사용되는 GeometricWrapper와 Geometric 계층구조들은 협력하는 계층구조로서 래퍼가 Adapter (105)의 계층구조가 된다. 정책들이 Strategy (339)의 계층구조인 Window와 WindowDisplayPolicy 계층구조도 마찬가지로 협력하는 계층구조이다.
Yelland (1996)는 Bridge 패턴을 이용해 비주얼웍스의 제안 버전을 설명하고 있는데, 이 버전에서는 위젯을 Proxy (213)를 개조하는 플랫폼 특정적 구현 객체와 플랫폼 중립적 인터페이스 객체로 나눈다. 하지만 이는 Bridge 패턴이 아니라 사실상 두 개의 협력 계층구조 집합이다. 첫 번째 집합은 Widget/WidgetBridge이고, 두 번째 집합은 Widgetbridge/WidgetProxy이다. 첫 번째 계층구조는 각 위젯이 그 값의 표시 방법에 관한 전략이 되는 Strategy 계층구조이다. 두 번째 계층구조는 Adapter들의 집합이고, 세 번째 계층구조는 자체 위젯을 스스로 감싸는 Proxy들의 집합이다.
비주얼웍스에서 계층구조들의 View-Controller 쌍은 협력하는 계층구조ㅡ두 Strategy 계층구조ㅡ이다. 이와 비슷하게 앞에서 설명했듯이 비주얼웍스에서 ValueModel과 값 기반의 하위뷰 쌍들 또한 협력하는 계층구조이다; 하위뷰들은 Strategy가 되고 ValueModel들은 Adapter가 된다.
관련 패턴
Bridge 패턴 대 Decorator 패턴
Bridge와 Decorator (161)는 동일한 문제, 서브클래싱을 해결하기 위해 달려든다. 서로 다른 두 개의 클래스에서 동일한 서브클래스를 구현해야 할 경우 문제에 직면할 것이다. Decorator는 서브클래스의 추가 행위(extra behavior)를 추가하기 위해 기본 클래스의 인스턴스 주위를 감쌀 수 있는 구분된 클래스로 추가 행위를 추출함으로써 문제를 해결한다. 이를 위해선 모든 클래스가 동일한 인터페이스를 가져야 한다. 이는 하나의 구체적 컴포넌트 상위에 다수의 데코레이터가 서로 중첩될 수 있도록 한다.
Bridge 패턴은 원래(original) 클래스로부터 모든 구현부 내용을 추출하여 구분된 Implementor 계층구조로 이동시킨다. 그리고 난 후 원래 계층구조를 재구현하여 서브타입을 나타내는 Abstraction 집합으로 만들고 그들의 구현을 Implementor로 위임한다. Abstraction 서브클래스는 확장된 인터페이스를 가질 수 있지만 Decorator와 같이 중첩될 순 없다. 하지만 Bridge 패턴은 반복 적용이 가능하여 한 예제에서는 Implementor 계층구조였는데 다음 예제에선 Abstraction 계층구조로 발견되기도 한다.
Bridge와 Proxy
LaLonde와 Pugh (1995)는 Proxy가 Abstraction이 되고 Object가 Implementor가 되어 Proxy (213)를 스몰토크에서 Bridge로서 구현시킬 수 있다고 언급했다.
협력하는 계층구조
구조 패턴과 행동 패턴에서 이름이 같은 클래스를 계층구조에 구현할 수 있는데, 이 계층구조는 협력하는 계층구조 쌍의 일부인 경우가 주로이다. 예를 들어, GeometricWrapper는 그의 Geometric에 대한 Adapter (105)이고, WindowDisplayPolicy는 그의 Window에 대한 Strategy (339)이다.