Search
🎨

[디자인 패턴 - 구조 패턴] 브릿지 패턴 (Bridge)

구현과 추상을 분리하고 싶을 때

1. 의도

구현에서 추상을 분리하여 이들을 독립적으로 다양성을 가질 수 있도록 합니다.

2. 활용성

GoF
추상적인 개념과 이에 대한 구현 사이의 지속적인 종속관계를 피하고 싶을 때 사용
런타임에 구현 방법을 선택하거나, 구현 내용을 변경하고 싶을 때 해당
추상적 개념과 구현 모두가 독립적인 서브클래싱을 통해 확장되어야할 때 사용
개발자가 구현을 또 다른 추상적인 개념과 연결할 수 있게 할 뿐 아니라, 각각을 독립적으로 확장 가능하게 함
추상적 개념에 대한 구현 내용을 변경하는 것이 다른 관련 프로그램에 영향을 주지 않아야할 때 사용
추상적 개념에 해당하는 클래스를 사용하는 코드들은 구현 클래스가 변경되었다고 해서 다시 컴파일 되지 않아야함
클래스 계통에서 클래스 수가 급증하는 것을 방지하고자 할 때
여러 객체들에 걸쳐 구현을 공유하고자 하며, 이런 사실을 사용자쪽에 공개하고 싶지 않을 때
Head First
활용
여러 플랫폼에서 ㅅ용해야하는 그래픽스와 윈도우 처리 시스템에 유용히 쓰임
인터페애스와 실제 구현할 부분을 서로 다른 방식으로 변경해야할 때 유용
장점
구현과 인터페이스를 완전히 결합하지 않았기에 구현과 추상화 부분을 분리할 수 있음
추상화된 부분과 실제 구현 부분을 독립적으로 확장 가능
추상화 부분을 구현한 구상 클래스가 바뀌어도 클라이언트에 영향 없음
단점
디자인 복잡도 증가

3. 구조

UML Class Diagram

4. 참여자

참여자
역할
예시
Abstraction
추상적 개념에 대한 인터페이스 제공 객체 구현자(Implementor)에 대한 참조자 관리
TV
RefiendAbstraction
추상적 개념에 정의된 인터페이스 확장
삼성 TV, LG TV
Implementor
구현 클래스에 대한 인터페이스 제공 - 실질적인 구현을 제공한 서브클래스들에 공통적인 연산 시그니처만을 정의 - Abstraction 클래스에 정의된 인터페이스에 정확히 대응할 필요 없음 - 두 인터펭스는 서로 다른 형태일 수 있음
리모콘
ConcreteImplementor
인터페이스를 구현하는 것으로 실제적인 구현 내용을 담음
TV 리모콘, 에어컨 리모콘

5. 협력 방법

Abstraction 클래스가 사용자 요청을 Implementor 객체에 전달합니다.

6. 결과

인터페이스와 구현 분리
구현이 인터페이스에 얽매이지 않게 됨
추상적 개념에 대한 어떤 방식의 구현을 택할지가 런타임에 결정될 수 있음
Absrtraction과 Implementor의 분리는 컴파일 타임 의존성 제거 가능
구현을 변경하더라도 추상적 개념에 대한 클래스를 다시 컴파일할 필요가 없음
추상적 개념 클래스와 관련된 다른 코드 역시도 다시 컴파일 할 필요가 없음
계층화 가능
시스템의 상위 수준 영역에서는 Abstraction과 Implementor만 알면 됨
확장성 제고
Abstraction과 Implementor를 독립적으로 확장 가능
구현 세부사항을 사용자에게 숨기기
상세 구현 내용을 사용자에게 은닉 할 수 있음

7. 예시 코드

// Client code const htmlRenderer = new HTMLRenderer(); const button = new Button(htmlRenderer); button.render(); // logs "Rendering button in HTML" const canvasRenderer = new CanvasRenderer(); const input = new Input(canvasRenderer); input.render(); // logs "Rendering input in Canvas" const svgRenderer = new SVGRenderer(); const label = new Label(svgRenderer); label.render(); // logs "Rendering label in SVG"
TypeScript
복사
// Implementor abstract class AbstractUIElement { protected renderer: Renderer; constructor(renderer: Renderer) { this.renderer = renderer; } public abstract render(): void; } class Button extends AbstractUIElement { public render(): void { this.renderer.renderButton(); } } class Input extends AbstractUIElement { public render(): void { this.renderer.renderInput(); } } class Label extends AbstractUIElement { public render(): void { this.renderer.renderLabel(); } }
TypeScript
복사
// Abstraction interface Renderer { renderButton(): void; renderInput(): void; renderLabel(): void; } class HTMLRenderer implements Renderer { public renderButton(): void { console.log("Rendering button in HTML"); } public renderInput(): void { console.log("Rendering input in HTML"); } public renderLabel(): void { console.log("Rendering label in HTML"); } } class CanvasRenderer implements Renderer { public renderButton(): void { console.log("Rendering button in Canvas"); } public renderInput(): void { console.log("Rendering input in Canvas"); } public renderLabel(): void { console.log("Rendering label in Canvas"); } } class SVGRenderer implements Renderer { public renderButton(): void { console.log("Rendering button in SVG"); } public renderInput(): void { console.log("Rendering input in SVG"); } public renderLabel(): void { console.log("Rendering label in SVG"); } }
TypeScript
복사
Head First based example
const tv = new TV(); const tvRemoteControl = new TVRemoteControl(tv); tv.enable(); console.log(`TV enabled: ${tv.isEnabled()}`); // true tvRemoteControl.buttonOnePressed(); console.log(`TV volume: ${tv.getVolume()}`); // 11 tvRemoteControl.buttonTwoPressed(); console.log(`TV volume: ${tv.getVolume()}`); // 10
TypeScript
복사
// Implementor abstract class RemoteControl { protected device: Device; constructor(device: Device) { this.device = device; } abstract buttonOnePressed(): void; abstract buttonTwoPressed(): void; } class TVRemoteControl extends RemoteControl { buttonOnePressed(): void { if (this.device.isEnabled()) { const currentVolume = this.device.getVolume(); this.device.setVolume(currentVolume + 1); } } buttonTwoPressed(): void { if (this.device.isEnabled()) { const currentVolume = this.device.getVolume(); this.device.setVolume(currentVolume - 1); } } }
TypeScript
복사
// Abstraction interface Device { isEnabled(): boolean; enable(): void; disable(): void; getVolume(): number; setVolume(volume: number): void; getChannel(): number; setChannel(channel: number): void; } class TV implements Device { private enabled = false; private volume = 10; private channel = 1; isEnabled(): boolean { return this.enabled; } enable(): void { this.enabled = true; } disable(): void { this.enabled = false; } getVolume(): number { return this.volume; } setVolume(volume: number): void { this.volume = volume; } getChannel(): number { return this.channel; } setChannel(channel: number): void { this.channel = channel; } }
TypeScript
복사