데코레이터 decorator 패턴 - 자바스크립트 예제
요약
데코레이터 decorator 패턴을 자바스크립트 코드와 함께 알아봅니다. 정보처리기사 대비 문제가 포함되어있습니다.
데코레이터 패턴 요약
패턴 종류 | 핵심 키워드 |
---|---|
데코레이터 (Decorator) | 동적으로 기능 추가, 객체 조합, 상속의 대안 |

데코레이터 (Decorator) 패턴
데코레이터 패턴은 객체의 기존 코드를 수정하지 않고도 동적으로 새로운 기능이나 책임을 추가할 수 있게 해주는 구조 패턴입니다. 이름처럼 객체를 장식(decorate)하여 기능을 확장하는 방식입니다. 상속을 통해 기능을 확장할 수도 있지만, 상속은 정적인 방식이라 모든 조합에 대한 서브클래스를 만들어야 하는 '클래스 폭발(class explosion)' 문제를 야기할 수 있습니다. 데코레이터 패턴은 이러한 문제를 해결하는 유연한 대안입니다.
현실 세계의 예로 '커피 주문'을 생각해볼 수 있습니다. 기본 커피(아메리카노)에 우유를 추가하고(카페라떼), 거기에 시럽을 추가하고(바닐라 라떼), 휘핑 크림을 올리는(아인슈페너)처럼, 기본 객체에 여러 '장식(옵션)'을 동적으로 추가해 새로운 객체를 만드는 것과 같습니다.
기본 구조
데코레이터 패턴은 다음 요소들로 구성됩니다.
- Component: 장식될 객체와 장식하는 객체(데코레이터)가 모두 구현해야 할 공통 인터페이스(또는 추상 클래스)입니다.
- ConcreteComponent: 장식의 대상이 되는 핵심 기능을 가진 구체적인 클래스입니다. (예: 기본 커피)
- Decorator:
Component
인터페이스를 구현하면서, 내부에Component
객체에 대한 참조를 가집니다. 이 참조를 통해 장식할 객체와 연결됩니다. - ConcreteDecorator:
Decorator
를 상속받아 실제로 새로운 기능이나 책임을 추가하는 클래스입니다. (예: 우유 추가, 시럽 추가)
예시: 커피 주문 시스템
다양한 옵션을 추가할 수 있는 커피 주문 시스템을 만들어 보겠습니다.
먼저 모든 커피 객체가 따를 Coffee
인터페이스(여기서는 클래스로 정의)를 만듭니다.
// Component: 공통 인터페이스
class Coffee {
cost() {
throw new Error("cost()는 반드시 구현되어야 합니다.");
}
getDescription() {
throw new Error("getDescription()은 반드시 구현되어야 합니다.");
}
}
가장 기본이 되는 커피, SimpleCoffee
를 만듭니다.
// ConcreteComponent: 장식될 기본 객체
class SimpleCoffee extends Coffee {
cost() {
return 5; // 기본 커피 가격 5
}
getDescription() {
return "기본 커피";
}
}
이제 데코레이터의 기반이 될 추상 클래스를 만듭니다. 이 클래스는 다른 Coffee
객체를 감쌀(wrap) 것입니다.
// Decorator: 다른 Coffee 객체를 감싸는 역할
class CoffeeDecorator extends Coffee {
constructor(coffee) {
super();
this._coffee = coffee; // 감쌀 Coffee 객체를 저장
}
cost() {
return this._coffee.cost(); // 감싼 객체의 cost()를 호출
}
getDescription() {
return this._coffee.getDescription(); // 감싼 객체의 getDescription()을 호출
}
}
이제 구체적인 데코레이터들을 만듭니다. WithMilk
와 WithSugar
는 각각 우유와 설탕을 추가하는 역할을 합니다.
// ConcreteDecorator A: 우유 추가
class WithMilk extends CoffeeDecorator {
constructor(coffee) {
super(coffee);
}
cost() {
return super.cost() + 2; // 기본 가격에 우유 가격 추가
}
getDescription() {
return super.getDescription() + ", 우유 추가";
}
}
// ConcreteDecorator B: 설탕 추가
class WithSugar extends CoffeeDecorator {
constructor(coffee) {
super(coffee);
}
cost() {
return super.cost() + 1; // 기본 가격에 설탕 가격 추가
}
getDescription() {
return super.getDescription() + ", 설탕 추가";
}
}
// 클라이언트 코드
let coffee = new SimpleCoffee();
console.log(`${coffee.getDescription()} - 가격: ${coffee.cost()}`);
// 출력: 기본 커피 - 가격: 5
// 우유를 추가해봅시다.
coffee = new WithMilk(coffee);
console.log(`${coffee.getDescription()} - 가격: ${coffee.cost()}`);
// 출력: 기본 커피, 우유 추가 - 가격: 7
// 거기에 설탕도 추가해봅시다.
coffee = new WithSugar(coffee);
console.log(`${coffee.getDescription()} - 가격: ${coffee.cost()}`);
// 출력: 기본 커피, 우유 추가, 설탕 추가 - 가격: 8
// 순서를 바꿔서 설탕을 먼저 추가할 수도 있습니다.
let anotherCoffee = new SimpleCoffee();
anotherCoffee = new WithSugar(anotherCoffee);
anotherCoffee = new WithMilk(anotherCoffee);
console.log(
`${anotherCoffee.getDescription()} - 가격: ${anotherCoffee.cost()}`
);
// 출력: 기본 커피, 설탕 추가, 우유 추가 - 가격: 8
이처럼 데코레이터 패턴을 사용하면, SimpleCoffee
클래스를 전혀 수정하지 않고도 WithMilk
, WithSugar
같은 새로운 기능을 동적으로 계속 추가할 수 있습니다. 만약 '휘핑 크림 추가', '샷 추가' 등의 기능이 필요하다면 새로운 데코레이터 클래스만 만들어주면 됩니다.
데코레이터 패턴 중요 키워드
- 객체를 감싸서(wrap) 새로운 책임을 추가합니다.
- 객체의 결합(SimpleCoffee, WithMilk, WithSugar)을 통해 기능을 확장합니다.
- 상속의 유연한 대안입니다.
- 동적으로 기능을 추가하거나 제거할 수 있습니다.
- 핵심 기능과 추가 기능이 동일한 인터페이스를 따릅니다.
- 데코레이터의 순서가 결과에 영향을 줄 수 있습니다.
문제 | 객체의 기존 코드를 수정하지 않고도 동적으로 새로운 기능이나 책임을 추가할 수 있게 해주는 패턴으로, 객체를 감싸서(wrap) 기능을 확장하며 상속의 유연한 대안을 제공하는 디자인 패턴은? |
보기 | |
답변 | |
정답 | 정답 보기 |