데코레이터 패턴이란?
데코레이터 패턴(Decorator Pattern)은 객체에 추가 요소를 동적으로 추가할 수 있다.
상속으로 확장하는 것보다 새로운 인스턴스 객체를 만들어 감싸는 데코레이터 패턴이 유연성 측면에서 더 좋다.
Starbuzz Coffee 예제
Starbuzz라는 초대형 커피 전문점이 있다.
이 카페의 초기 주문 시스템은 이렇게 되어 있었다.


초창기에는 4가지의 커피 종류가 있었다. 점점 시간이 지날 수록 커피에 휘핑, 우유, 모카 등을 추가하는 손님이 생겨났고 하나씩 확장하다 보니 오른쪽 그림과 같은 다이어그램이 만들어졌다.
한 눈에 봐도 문제가 많은 걸 알 수 있다.
인스턴스 변수와 상속을 통해 이를 해결하고자 했다.

boolean타입의 인스턴스 변수를 만들고 이에 대한 getter와 setter를 만들어서 Condiment(첨가물)이 들어갔는지 안 들어갔는지 확인하는 방식이다.
이러한 디자인에는 여러 문제점이 존재한다.
- 첨가물의 가격이 바뀔 때마다 코드를 수정해야 한다.
- 첨가물의 종류가 많아지면 새로운 메서드를 추가해야 하고, cost() 메서드도 수정해야 한다.
- 새로운 음료가 추가 될 경우에 상속 받아서는 안되는 메서드도 상속을 받게 된다.
즉, 확장에는 열려있지만, 수정에는 닫혀 있어야 하는 OCP 원칙을 위반하는 것이다.
이를 해결하기 위해 Decorator Pattern이 존재한다.
데코레이터 패턴은 말 그대로 초기 객체를 대상으로 내가 원하는 것을 추가하여 꾸며주는 것이다.



원본인 다크로스트가 있고, 거기에 모카를 추가하고, 그 위에 휘핑을 올리는 식으로 감싸준다.

가격을 구할 때는 가장 바깥 쪽에 있는 휘핑의 cost 메서드를 호출한다. (감쌀 때마다 계속해서 가산이 되기 때문)
이러한 형태의 프레임워크를 다이어그램으로 나타내면 다음과 같다.


Berverage를 상속하는 커피 종류와 첨가물 데코레이터를 만든다.
첨가물 데코레이터를 상속하는 첨가물의 종류를 만든다.
다이어그램을 보면 ComdimentDecorator이 상속으로 이루어져 있기는 하지만, 이 데코레이터의 형식이 데코레이터로 감싸는 객체의 형식과 같다는 점이 중요하다.
또한, 상속을 이용하여 형식을 맞추는 것이지 행동을 물려받는 것이 아니기 때문에 위에서 나열했던 문제점이 해결된다.
코드
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
가장 상위 추상 클래스인 Beverage이다.
메뉴를 설명하는 getDescription()이 존재하고 cost() 하위 클래스에서 구현해야 하는 추상 메서드가 존재한다.
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "Dark Roast Coffee"; // Beverage에서 상속받은 인스턴스 변수이다
}
public double cost() {
return .99;
}
}
Beverage를 상속 받는 DarkRoast 클래스이다.
Beverage에서 상속 받은 description을 재정의 해줬고, 오버라이드 된 cost() 메서드를 구현해서 .99$를 추가했다.
public abstract class CondimentDecorator extends Beverage {
Beverage beverage; // 슈퍼클래스 유형
public abstract String getDescription();
}
Beverage를 상속 받는 데코레이터인 CondimentDecorator이다.
어떤 음료든 감쌀 수 있도록 슈퍼클래스 유형을 사용한다.
getDescription을 추상 클래스로 선언하여 첨가물 클래스에도 어떤 첨가물이 들어가는지 알기 위한 메서드를 구현하도록 한다.
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
public double cost() {
return .20 + beverage.cost();
}
}
CondimentDecorator를 상속 받는 Mocha 클래스이다.
현재까지 만들어진 beverage에 Mocha를 추가하고 가격도 더해줘서 return한다.
public class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
public double cost() {
return .10 + beverage.cost();
}
}
마찬가지로 Whip 클래스도 만들어준다.
실행코드
public class StarbuzzCoffee {
public static void main(String args[]) {
Beverage beverage2 = new Whip(new Mocha(new Mocha(new DarkRoast())));
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());
}
}
먼저 Whip 클래스가 호출돼서 안쪽으로 들어가보면 인자값으로 받는 Beverage 객체의 description과 cost를 호출하게 되어 있다. 결국 가장 먼저 메모리에 저장되는 것은 DarkRoast()의 description과 cost일 것이다.
즉, 하나의 원본 객체를 첨가물(장식품)으로 계속해서 감싸줘서 하나의 객체 덩어리로 만드는 것이 Decorator Pattern이다.
이 예제에서는 DarkRoast를 Mocha로 2번 감싸주고 마지막으로 전체를 Whip으로 감싸주는 구조이다.
장단점
장점: 객체에 추가 요소를 동적으로 더할 수 있고, 서브클래스를 만들 때보다 훨씬 유연하게 기능을 확장할 수 있다.
단점: 자잘한 클래스가 엄청나게 많이 추가되는 경우가 있다. 이러한 경우 사람이 봤을 때 이해하기 어려운 디자인이 된다.
'Design Pattern' 카테고리의 다른 글
[Headfirst Design Pattern] 복합 패턴(Compound Patterns) (0) | 2024.06.13 |
---|---|
[Headfirst Design Pattern] 템플릿 메소드 패턴(Template Method Pattern) (0) | 2024.06.06 |
[Headfirst Design Parttern] 어댑터 패턴&퍼사드 패턴(Adapter Pattern & Facade Pattern) (0) | 2024.06.05 |
[Headfirst Design Pattern] 옵저버 패턴(Observer Pattern) (0) | 2024.04.12 |
데코레이터 패턴이란?
데코레이터 패턴(Decorator Pattern)은 객체에 추가 요소를 동적으로 추가할 수 있다.
상속으로 확장하는 것보다 새로운 인스턴스 객체를 만들어 감싸는 데코레이터 패턴이 유연성 측면에서 더 좋다.
Starbuzz Coffee 예제
Starbuzz라는 초대형 커피 전문점이 있다.
이 카페의 초기 주문 시스템은 이렇게 되어 있었다.


초창기에는 4가지의 커피 종류가 있었다. 점점 시간이 지날 수록 커피에 휘핑, 우유, 모카 등을 추가하는 손님이 생겨났고 하나씩 확장하다 보니 오른쪽 그림과 같은 다이어그램이 만들어졌다.
한 눈에 봐도 문제가 많은 걸 알 수 있다.
인스턴스 변수와 상속을 통해 이를 해결하고자 했다.

boolean타입의 인스턴스 변수를 만들고 이에 대한 getter와 setter를 만들어서 Condiment(첨가물)이 들어갔는지 안 들어갔는지 확인하는 방식이다.
이러한 디자인에는 여러 문제점이 존재한다.
- 첨가물의 가격이 바뀔 때마다 코드를 수정해야 한다.
- 첨가물의 종류가 많아지면 새로운 메서드를 추가해야 하고, cost() 메서드도 수정해야 한다.
- 새로운 음료가 추가 될 경우에 상속 받아서는 안되는 메서드도 상속을 받게 된다.
즉, 확장에는 열려있지만, 수정에는 닫혀 있어야 하는 OCP 원칙을 위반하는 것이다.
이를 해결하기 위해 Decorator Pattern이 존재한다.
데코레이터 패턴은 말 그대로 초기 객체를 대상으로 내가 원하는 것을 추가하여 꾸며주는 것이다.



원본인 다크로스트가 있고, 거기에 모카를 추가하고, 그 위에 휘핑을 올리는 식으로 감싸준다.

가격을 구할 때는 가장 바깥 쪽에 있는 휘핑의 cost 메서드를 호출한다. (감쌀 때마다 계속해서 가산이 되기 때문)
이러한 형태의 프레임워크를 다이어그램으로 나타내면 다음과 같다.


Berverage를 상속하는 커피 종류와 첨가물 데코레이터를 만든다.
첨가물 데코레이터를 상속하는 첨가물의 종류를 만든다.
다이어그램을 보면 ComdimentDecorator이 상속으로 이루어져 있기는 하지만, 이 데코레이터의 형식이 데코레이터로 감싸는 객체의 형식과 같다는 점이 중요하다.
또한, 상속을 이용하여 형식을 맞추는 것이지 행동을 물려받는 것이 아니기 때문에 위에서 나열했던 문제점이 해결된다.
코드
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
가장 상위 추상 클래스인 Beverage이다.
메뉴를 설명하는 getDescription()이 존재하고 cost() 하위 클래스에서 구현해야 하는 추상 메서드가 존재한다.
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "Dark Roast Coffee"; // Beverage에서 상속받은 인스턴스 변수이다
}
public double cost() {
return .99;
}
}
Beverage를 상속 받는 DarkRoast 클래스이다.
Beverage에서 상속 받은 description을 재정의 해줬고, 오버라이드 된 cost() 메서드를 구현해서 .99$를 추가했다.
public abstract class CondimentDecorator extends Beverage {
Beverage beverage; // 슈퍼클래스 유형
public abstract String getDescription();
}
Beverage를 상속 받는 데코레이터인 CondimentDecorator이다.
어떤 음료든 감쌀 수 있도록 슈퍼클래스 유형을 사용한다.
getDescription을 추상 클래스로 선언하여 첨가물 클래스에도 어떤 첨가물이 들어가는지 알기 위한 메서드를 구현하도록 한다.
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
public double cost() {
return .20 + beverage.cost();
}
}
CondimentDecorator를 상속 받는 Mocha 클래스이다.
현재까지 만들어진 beverage에 Mocha를 추가하고 가격도 더해줘서 return한다.
public class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
public double cost() {
return .10 + beverage.cost();
}
}
마찬가지로 Whip 클래스도 만들어준다.
실행코드
public class StarbuzzCoffee {
public static void main(String args[]) {
Beverage beverage2 = new Whip(new Mocha(new Mocha(new DarkRoast())));
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());
}
}
먼저 Whip 클래스가 호출돼서 안쪽으로 들어가보면 인자값으로 받는 Beverage 객체의 description과 cost를 호출하게 되어 있다. 결국 가장 먼저 메모리에 저장되는 것은 DarkRoast()의 description과 cost일 것이다.
즉, 하나의 원본 객체를 첨가물(장식품)으로 계속해서 감싸줘서 하나의 객체 덩어리로 만드는 것이 Decorator Pattern이다.
이 예제에서는 DarkRoast를 Mocha로 2번 감싸주고 마지막으로 전체를 Whip으로 감싸주는 구조이다.
장단점
장점: 객체에 추가 요소를 동적으로 더할 수 있고, 서브클래스를 만들 때보다 훨씬 유연하게 기능을 확장할 수 있다.
단점: 자잘한 클래스가 엄청나게 많이 추가되는 경우가 있다. 이러한 경우 사람이 봤을 때 이해하기 어려운 디자인이 된다.
'Design Pattern' 카테고리의 다른 글
[Headfirst Design Pattern] 복합 패턴(Compound Patterns) (0) | 2024.06.13 |
---|---|
[Headfirst Design Pattern] 템플릿 메소드 패턴(Template Method Pattern) (0) | 2024.06.06 |
[Headfirst Design Parttern] 어댑터 패턴&퍼사드 패턴(Adapter Pattern & Facade Pattern) (0) | 2024.06.05 |
[Headfirst Design Pattern] 옵저버 패턴(Observer Pattern) (0) | 2024.04.12 |