데커레이터 패턴 정의
데커레이터는 어떤 기능에 추가적으로 기능을 덧붙이고 싶은 경우, 그 기능들을 Decorator로 만들어서 덧붙이는 방식입니다.
예시
토스트를 만들때 일반빵, 밀빵, 버터빵, 우유빵 선택사항이 존재하고 토핑으로 햄, 야채, 치즈, 달걀, 새우 그리고 마지막으로 소스로 마요네즈, 케찹, 칠리가 존재한다고 할때 토핑과 소스가 데커레이터가 됩니다.
왜냐면 보통 토스트는 빵(1개)+토핑(여러개)+소스(여러종류) 로 이루어지기 때문에 빵을 제외한 토핑과 소스를
더 추가 할 수 있는 데커레이터로 표현합니다.
장점
- 확장성(Extensibility): 데커레이터 패턴은 기존 클래스를 수정하지 않고도 새로운 기능을 추가할 수 있다. 즉 코드의 확정성을 향상시켜 새로운 동작을 간단하게 추가한다.
- 유연성(Flexibility): 객체에 여러 데커레이터를 조합하여 다양한 동작 및 결과물을 만들 수 있다. 이로써 다양한 조합으로 객체를 구성할 수 있어 유연성이 높아진다.
- 단일 책임 원칙(Single Responsibility Principle): 각 데커레이터는 특정한 책임을 담당하므로, 단일 책임 원칙을 따르는 디자인을 할 수 있다.
- 재사용성(Reusability): 데커레이터는 기능을 추가하는데 사용되므로 이를 다른 객체어 적용하여 재사용할 수 있다.
단점
- 복잡성(Complexity): 데커레이터 패턴은 객체의 계층 구조를 만들어내므로, 클래스의 수가 늘어날수록 코드의 복잡성이 증가한다.
- 순서 의존성(Order Dependency): 데커레이터들은 특정한 순서로 적용되어야 할 수 있습니다. 예시로 빵 고르고 토핑 핑, 소스 순이라면 그 순서를 지켜야 한다.
- 성능 오버헤드(Performance Overhead): 많은 수의 데커레이터가 사용될 경우, 실행 시간에 성능 오버헤드가 발생할 있다.
- 디버깅 어려움(Debugging Complexity): 객체의 동작이 여러 데커레이터를 통해 결정되므로, 디버깅이 어려워질 수 있다.
패턴 적용전
토핑을 추가 할 수 있는 빵이 있다고 존재하자. 이것을 따로 데커레이터로 구분 짓지 않고 코드를 짤 시에는
아래와 같이 된다.
Toast.java
public class Toast {
String name = "";
public void make() {
name += "빵";
}
public String getName() {
return name;
}
}
ToastWithEgg.java
public class ToastWithEgg extends Toast{
String name = "";
public void make() {
name += "에그빵";
}
public String getName() {
return name;
}
}
Main.java
public class Main {
public static void main(String args[]) {
Toast toast1 = new Toast();
toast1.make();
System.out.println(toast1.getName());
Toast toast2 = new ToastWithEgg();
toast2.make();
System.out.println(toast2.getName());
}
}
지금 당장은 문제가 없어보이지만 재료가 추가될수록 클래스가 기하급수적으로 늘게 된다. 당장
재료가 햄,달걀,새우,양배추,토마토 이런식으로 10개만 되더라도 클래스는 1024개가 되어야한다.(ex. 햄달걀 토스트, 달걀 토스트, 새우햄달걀 토스트, 토마토 토스트) 이 모든게 각각 클래스로 존재해야한다.
이것을 해결하고자 데커레이터 패턴을 적용해서 토핑의 갯수가 늘거나 빵종류가 늘더라도 클래스가 여러개씩 증가하는게 아니라 1개씩 추가되는것 만큼만 증가되도록 한다.
패턴 적용 후
Toast.java
public abstract class Toast {
String name = ""; //bread
int kcal = 0; //칼로리
public Toast() {
}
public void serve() {
getName();
getKcal();
}
public void getName() {
System.out.println("주문한 토스트 : "+name);
}
public void getKcal() {
System.out.println("칼로리 : "+kcal+"Kcal");
}
public void addTopping(ToppingDecorator toppingDecorator) {
addName(toppingDecorator.name);
addKcal(toppingDecorator.kcal);
}
public void addName(String toppingName) {
name = toppingName+name;
}
public void addKcal(int toppingKcal) {
kcal += toppingKcal;
}
}
NoramlBread.java
public class ButterBread extends Toast{
public ButterBread() {
name = " 버터식빵 토스트";
kcal = 400;
}
}
ToppingDecorator.java
public abstract class ToppingDecorator {
String name ="";
int kcal = 0;
public ToppingDecorator() {
}
}
Egg.java
public class Egg extends ToppingDecorator{
public Egg() {
name = "달걀";
kcal = 100;
}
}
이런식으로 빵 종류가 추가되면 추가되는 빵종류를 클래스로 만든다.
재료도 같은 방식으로 클래스 하나만 추가한다. 이전처럼 재료 하나추가될때 모든 조합을 다 클래스로 추가 하지 않는다.
고객의 요청이 있을때 아래와 같이 코드가 짜여지면 된다.
Main.java
public class Main {
public static void main(String args[]) {
Toast toast1 = new NormalBread();
toast1.addTopping(new Cheese());
toast1.addTopping(new Ham());
toast1.serve();
Toast toast2 = new WheatBread();
toast2.addTopping(new Vegetable());
toast2.addTopping(new Egg());
toast2.serve();
Toast toast3 = new ButterBread();
toast3.addTopping(new Ham());
toast3.addTopping(new Ham());
toast3.addTopping(new Ham());
toast3.serve();
}
}
결과
이제는 빵선택후 addTopping이라는 메서드를 통해서 재료들을 추가할 수 있다. 이를 통해
불필요한 여러클래스를 생성할 필요가 없이 존재하는 재료, 빵만 클래스가 있으면 된다.
'디자인 패턴' 카테고리의 다른 글
[디자인 패턴] 팩토리 메서드 패턴 (0) | 2023.12.03 |
---|---|
[디자인 패턴] 템플릿 메서드 패턴 (1) | 2023.12.01 |
[디자인패턴] 옵저버 패턴 (0) | 2023.10.31 |
[디자인패턴] 커맨드 패턴 (0) | 2023.10.29 |
[디자인패턴]빌더 패턴 (0) | 2023.10.12 |