구현과 추상을 분리하고 싶을 때
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
복사