Bridge는 큰 클래스 또는 밀접하게 관련된 클래스 집합을 서로 독립적으로 개발할 수 있는 추상화 및 구현이라는 두개의 개별 계층으로 분할 할 수 있는 구조적 디자인 패턴입니다.
한 쌍의 subclasses인 Circle
및 Square
가있는 Shape
클래스가 있다고 가정 해 보겠습니다. 이 클래스 계층 구조를 확장하여 색상을 통합하려고 하므로 빨강 및 파랑 모양 subclasses를 만들 계획입니다. 그러나 이미 2개의 subclasses가 있으므로 BlueCircle
및 RedSquare
와 같은 4개의 클래스 조합을 만들어야합니다.
새로운 모양 유형과 색상을 계층 구조에 추가하면 기하급수적으로 늘어납니다. 예를 들어 삼각형 모양을 추가하려면 각 색상에 하나씩 두 개의 하위 subclasses를 도입해야합니다. 그 후 새 색상을 추가하려면 각 모양 유형에 대해 하나씩 세 개의 subclasses를 만들어야 합니다. 더 멀리 갈수록 더 나빠집니다.
이 문제는 모양 클래스를 양식 및 색상의 두 가지 독립된 차원으로 확장하려고 하기 때문에 발생합니다. 이것은 클래스 상속과 관련된 매우 일반적인 문제입니다.
Bridge 패턴은 상속에서 개체 구성으로 전환하여 이 문제를 해결하려고 합니다. 이것이 의미하는 바는 차원 중 하나를 별도의 클래스 계층으로 추출하여 원래 클래스가 하나의 클래스 내에 모든 상태 및 동작을 갖는 대신 새 계층의 객체를 참조하도록 한다는 것입니다.
이 접근 방식에 따라 색상 관련 코드를 Red
와 Blue
의 두 가지 subclass가 있는 자체 클래스로 추출 할 수 있습니다. 그런 다음 Shape
클래스는 색상 객체 중 하나를 가리키는 참조 필드를 가져옵니다. 이제 모양은 색상 관련 작업을 연결된 색상 객체에 위임 할 수 있습니다. 이 참조는 Shape
클래스와 Color
클래스 사이의 다리 역할을합니다. 이제부터는 새 색상을 추가 할 때 모양 계층을 변경할 필요가 없으며 그 반대의 경우도 마찬가지입니다.
GoF 책은 Bridge 정의의 일부로 Abstraction and Implementation이라는 용어를 소개합니다. 제 생각에는 용어가 너무 학문적으로 들리며 패턴이 실제보다 더 복잡해 보입니다. 모양과 색상이있는 간단한 예를 읽은 후 GoF 책의 무서운 단어 뒤에 숨겨진 의미를 해독 해 봅시다.
추상화 (인터페이스라고도 함)는 일부 엔터티에 대한 높은 수준의 제어 계층입니다. 이 레이어는 자체적으로 실제 작업을 수행해서는 안됩니다. 작업을 구현 계층 (플랫폼이라고도 함)에 위임해야합니다.
프로그래밍 언어의 인터페이스 또는 추상 클래스에 대해 이야기하는 것이 아닙니다. 이것들은 같은 것이 아닙니다.
실제 애플리케이션에 대해 이야기 할 때 추상화는 그래픽 사용자 인터페이스 (GUI)로 나타낼 수 있으며 구현은 GUI 계층이 사용자 상호 작용에 대한 응답으로 호출하는 기본 운영 체제 코드 (API) 일 수 있습니다.
일반적으로 이러한 앱을 두 개의 독립적인 방향으로 확장 할 수 있습니다.
최악의 시나리오에서 이 앱은 수백 개의 조건문이 코드 전체에서 다양한 API와 다양한 유형의 GUI를 연결하는 거대한 스파게티 그릇처럼 보일 수 있습니다.
특정 인터페이스-플랫폼 조합과 관련된 코드를 별도의 클래스로 추출하여 이 혼란에 질서를 가져올 수 있습니다. 그러나 곧 이러한 수업이 많이 있다는 것을 알게 될 것입니다. 새로운 GUI를 추가하거나 다른 API를 지원하려면 더 많은 클래스를 생성해야 하기 때문에 클래스 계층 구조가 기하 급수적으로 증가 할 것입니다.
이 문제를 Bridge 패턴으로 해결해 보겠습니다. 클래스를 두 개의 계층으로 나눌 것을 제안합니다.
추상화 개체는 앱의 모양을 제어하여 실제 작업을 연결된 구현 개체에 위임합니다. 공통 인터페이스를 따르는 한 서로 다른 구현을 상호 교환 할 수 있으므로 동일한 GUI가 Windows 및 Linux에서 작동 할 수 있습니다.
결과적으로 API 관련 클래스를 건드리지 않고도 GUI 클래스를 변경할 수 있습니다. 또한 다른 운영 체제에 대한 지원을 추가하려면 구현 계층 구조에서 하위 클래스를 만들어야합니다.
이 예제는 Bridge
패턴이 장치와 원격 제어를 관리하는 앱의 monolithic 코드를 분할하는데 어떻게 도움이 되는지 보여줍니다. Device
클래스는 구현으로 작동하는 반면 Remote
는 추상화로 작동합니다.
기본 원격 제어 클래스는 장치 개체와 연결하는 참조 필드를 선언합니다. 모든 리모컨은 일반 장치 인터페이스를 통해 장치와 작동하므로 동일한 리모컨이 여러 장치 유형을 지원할 수 있습니다.
장치 클래스와 독립적으로 원격 제어 클래스를 개발할 수 있습니다. 필요한 것은 새 원격 subclass를 만드는 것입니다. 예를 들어 기본 리모컨에는 버튼이 두 개만 있을 수 있지만 추가 배터리 또는 터치 스크린과 같은 추가 기능을 사용하여 확장 할 수 있습니다.
클라이언트 코드는 원격의 생성자를 통해 원하는 유형의 원격 제어를 특정 장치 개체와 연결합니다.
// The "abstraction" defines the interface for the "control"
// part of the two class hierarchies. It maintains a reference
// to an object of the "implementation" hierarchy and delegates
// all of the real work to this object.
class RemoteControl is
protected field device: Device
constructor RemoteControl(device: Device) is
this.device = device
method togglePower() is
if (device.isEnabled()) then
device.disable()
else
device.enable()
method volumeDown() is
device.setVolume(device.getVolume() - 10)
method volumeUp() is
device.setVolume(device.getVolume() + 10)
method channelDown() is
device.setChannel(device.getChannel() - 1)
method channelUp() is
device.setChannel(device.getChannel() + 1)
// You can extend classes from the abstraction hierarchy
// independently from device classes.
class AdvancedRemoteControl extends RemoteControl is
method mute() is
device.setVolume(0)
// The "implementation" interface declares methods common to all
// concrete implementation classes. It doesn't have to match the
// abstraction's interface. In fact, the two interfaces can be
// entirely different. Typically the implementation interface
// provides only primitive operations, while the abstraction
// defines higher-level operations based on those primitives.
interface Device is
method isEnabled()
method enable()
method disable()
method getVolume()
method setVolume(percent)
method getChannel()
method setChannel(channel)
// All devices follow the same interface.
class Tv implements Device is
// ...
class Radio implements Device is
// ...
// Somewhere in client code.
tv = new Tv()
remote = new RemoteControl(tv)
remote.togglePower()
radio = new Radio()
remote = new AdvancedRemoteControl(radio)
일부 기능의 여러 변형이있는 모 놀리 식 클래스를 나누고 구성하려는 경우 (예 : 클래스가 다양한 데이터베이스 서버에서 작동 할 수있는 경우) Bridge 패턴을 사용하십시오.
클래스가 커질수록 어떻게 작동하는지 파악하기가 더 어려워지고 변경하는 데 더 오래 걸립니다. 기능 변형 중 하나를 변경하려면 전체 클래스를 변경해야 할 수 있으며, 이로 인해 종종 오류가 발생하거나 몇 가지 중요한 부작용이 해결되지 않습니다.
Bridge 패턴을 사용하면 모 놀리 식 클래스를 여러 클래스 계층으로 분할 할 수 있습니다. 그런 다음 다른 계층의 클래스와 독립적으로 각 계층의 클래스를 변경할 수 있습니다. 이 접근 방식은 코드 유지 관리를 단순화하고 기존 코드를 손상시킬 위험을 최소화합니다.
여러 직교 (독립) 차원에서 클래스를 확장해야하는 경우 패턴을 사용하십시오.
Bridge는 각 차원에 대해 별도의 클래스 계층을 추출 할 것을 제안합니다. 원래 클래스는 모든 작업을 자체적으로 수행하는 대신 해당 계층에 속한 개체에 관련 작업을 위임합니다.
런타임에 구현을 전환 할 수 있어야하는 경우 Bridge를 사용하십시오.
선택 사항이지만 Bridge 패턴을 사용하면 추상화 내부의 구현 개체를 바꿀 수 있습니다. 필드에 새 값을 할당하는 것만 큼 쉽습니다.
그건 그렇고,이 마지막 항목은 많은 사람들이 Bridge를 전략 패턴과 혼동하는 주된 이유입니다. 패턴은 클래스를 구조화하는 특정 방법 이상이라는 것을 기억하십시오. 또한 의도와 해결중인 문제를 전달할 수 있습니다.
O 플랫폼에 독립적 인 클래스와 앱을 만들 수 있습니다.
O 클라이언트 코드는 높은 수준의 추상화와 함께 작동합니다. 플랫폼 세부 정보에 노출되지 않습니다.
O 개방 / 폐쇄 원칙. 서로 독립적으로 새로운 추상화와 구현을 도입 할 수 있습니다.
O 단일 책임 원칙. 추상화의 고수준 논리와 구현의 플랫폼 세부 사항에 집중할 수 있습니다.
X 응집력이 높은 클래스에 패턴을 적용하여 코드를 더 복잡하게 만들 수 있습니다.