객체에 추가적인 요소를 동적으로 추가한다. 데코레이터는 서브클래스를 만드는 것을 통해 기능을 유연하게 확장할 수 있는 패턴이다.
데코레이터 패턴을 설명할 때 가장 많은 예로 등장하는 카페 주문에 대해 살펴본다.
아메리카노를 주문하고 여러가지 재료를 첨가하는 상황이다.
우유추가
샷추가
크림추가
class Beverage {
cost() {
let total = 0;
if (hasMilk()) total += 300;
if (hasShot()) total += 500;
if (hasCream()) total += 300;
if (hasCookie()) total += 1000;
};
}
// 그리고 커피 클래스 에서는
class CaffeLatte extends Beverage {
constructor() {
super();
}
cost() {
return super.cost() + 5000;
};
}
이와같은 모습의 코드가 될것이다.
잘 만들었다고 생각할 수 있지만, 큰 문제점이 있다.
디자인원칙 OCP : 클래스는 확장자에 대해서는 열려있어야 하지만 코드 변경에 대해서는 닫혀있어야 한다. 자세히보기
즉 기존 코드는 건드리지 말고 확장을 통해서 새로운 행동을 간단하게 추가할 수 있어야 한다.
아래의 코드를 살펴보자
class Beverage {
let cost;
}
// 위 클래스는 건드리지 않습니다.
class DecoratorBeverage extends Beverage {
cost() {
throw 'You must be override this function!';
}
}
class Americano extends DecoratorBeverage {
cost() {
return 4000;
}
}
class CaffeLatte extends DecoratorBeverage {
cost() {
return 5000;
}
}
이번은 데코 클래스를 만들어줍니다
class Cream extends DecoratorBeverage {
constructor(beverage) {
this.beverage = beverage;
}
cost() {
return this.beverage.cst() + 300;
}
}
class Shot extends DecoratorBeverage {
constructor(beverage) {
this.beverage = beverage;
}
cost() {
return this.beverage.cst() + 500;
}
}
class Milk extends DecoratorBeverage {
constructor(beverage) {
this.beverage = beverage;
}
cost() {
return this.beverage.cst() + 300;
}
}
마지막으로 다음과 같이 사용한다.
const americano = new Americano();
americano = new Shot(americano);
americano = new Milk(americano);
console.log('cost : ', americano.cost()); // 4800