데코레이터 패턴
객체에 추가 요소를 동적으로 더할 수 있습니다.
데코레이터를 사용하면 서브클래스를 만들 때보다 훨씬 유연하게 기능을 확장할 수 있습니다.
저는 쉽게 접근해 봤는데 데코레이터는 장식하는 사람이라는 뜻을 가지는데, 마치 크리스마스트리에 추가 요소(양말, 종) 등을 장식하여 더 멋진 트리를 만들어주는 즉, 더 좋은 기능이 되도록 만들어 주는 패턴이라고 생각합니다.
데코레이터 패턴 사용 예제
저는 데코레티어 패턴에서 생각나는 주제가 2가지 있었는데 첫 번째는 크리스마스트리였고,두 번째는 피자였습니다. 그래서 예제는 제가 즐겨먹던 피자로 만들어 보겠습니다.
public abstract class Pizza {
String description = "피자";
public String getDescription() {
return description;
}
public abstract int cost();
}
public class SweetPotatoPizza extends Pizza {
public SweetPotatoPizza() {
description = "고구마 피자";
}
@Override
public int cost() {
return 21900;
}
}
public class Order {
public static void main(String[] args) {
Pizza sweetPotatoPizza = new SweetPotatoPizza();
System.out.println("주문: " + sweetPotatoPizza.getDescription());
System.out.println("가격: " + sweetPotatoPizza.cost() + "원");
}
}
// 주문: 고구마 피자
// 가격: 21900원
저는 항상 반올림피자샵 고구마 피자를 먹는데 이유는 반올림 피자샵에 소보로 엣지를 추가할 수 있기 때문입니다.
하지만 여기서 바로 주문하면 그냥 고구마 피자가 오기 때문에 소보로 엣지를 추가해 보겠습니다.
public abstract class PizzaDecorator extends Pizza {
public abstract String getDescription();
}
public class SoboroEdge extends PizzaDecorator {
private Pizza pizza;
public SoboroEdge(Pizza pizza) {
this.pizza = pizza;
}
@Override
public String getDescription() {
return pizza.getDescription() + " 소보루 엣지 변경";
}
@Override
public int cost() {
return pizza.cost() + 5000;
}
}
public class Order {
public static void main(String[] args) {
Pizza sweetPotatoPizza = new SweetPotatoPizza();
sweetPotatoPizza = new SoboroEdge(sweetPotatoPizza);
System.out.println("주문: " + sweetPotatoPizza.getDescription());
System.out.println("가격: " + sweetPotatoPizza.cost() + "원");
}
}
// 주문: 고구마 피자 소보루 엣지 변경
// 가격: 26900원
소보로 엣지를 추가했는데 오늘따라 베이컨이랑 치즈를 추가하고 싶어서 베이컨과 치즈를 추가해보겠습니다.
public class BaconTopping extends PizzaDecorator {
private Pizza pizza;
public BaconTopping(Pizza pizza) {
this.pizza = pizza;
}
@Override
public String getDescription() {
return pizza.getDescription() + " 베이컨 4장 추가";
}
@Override
public int cost() {
return pizza.cost() + 800;
}
}
public class CheeseTopping extends PizzaDecorator {
private Pizza pizza;
public CheeseTopping(Pizza pizza) {
this.pizza = pizza;
}
@Override
public String getDescription() {
return pizza.getDescription() + " 치즈 추가";
}
@Override
public int cost() {
return pizza.cost() + 3000;
}
}
public class Order {
public static void main(String[] args) {
Pizza sweetPotatoPizza = new SweetPotatoPizza();
sweetPotatoPizza = new SoboroEdge(sweetPotatoPizza);
sweetPotatoPizza = new BaconTopping(sweetPotatoPizza);
sweetPotatoPizza = new CheeseTopping(sweetPotatoPizza);
System.out.println("주문: " + sweetPotatoPizza.getDescription());
System.out.println("가격: " + sweetPotatoPizza.cost() + "원");
}
}
// 주문: 고구마 피자 소보루 엣지 변경 베이컨 4장 추가 치즈 추가
// 가격: 30700원
그런데 갑자기 누나들이 같이 먹자고 해서 라지로 변경하기로 결정했습니다.
public abstract class SizeDecorator extends Pizza {
public abstract String getDescription();
}
public class LargeSize extends SizeDecorator {
private Pizza pizza;
public LargeSize(Pizza pizza) {
this.pizza = pizza;
}
@Override
public String getDescription() {
return pizza.getDescription() + " 라지 변경";
}
@Override
public int cost() {
return pizza.cost() + 3000;
}
}
public class Order {
public static void main(String[] args) {
Pizza sweetPotatoPizza = new SweetPotatoPizza();
sweetPotatoPizza = new SoboroEdge(sweetPotatoPizza);
sweetPotatoPizza = new BaconTopping(sweetPotatoPizza);
sweetPotatoPizza = new CheeseTopping(sweetPotatoPizza);
sweetPotatoPizza = new LargeSize(sweetPotatoPizza);
System.out.println("주문: " + sweetPotatoPizza.getDescription());
System.out.println("가격: " + sweetPotatoPizza.cost() + "원");
}
}
// 주문: 고구마 피자 소보루 엣지 변경 베이컨 4장 추가 치즈 추가 라지 변경
// 가격: 33700원
이렇게 해서 제가 즐겨먹는 피자가 완성되었습니다!
데코레이터 패턴 사용 이유
예제를 먼저 보여드린 이유는
(소보로 엣지를 추가, 베이컨과 치즈를 추가, 라지로 변경)
데코레이터 패턴은 무엇을 추가하거나 변경한 거처럼 추가 요소를 동적으로 처리할 수 있고, 유연하게 기능을 확장할 수 있습니다.
예제에서 PizzaDecorator라는 토핑을 위한 클래스와 SizeDecorator라는 사이즈를 위한 클래스를 추상 클래스로 만들어 토핑 추가 및 사이즈 변경 등 확장성을 높일 수 있기 때문입니다.
즉, 개방 폐쇄의 원칙(OCP)을 잘 지킨 패턴이기 때문입니다.
개방 폐쇄의 원칙(Open/Closed Principle OCP)
새로운 기능을 추가하려면 기존 코드를 변경하지 말고 새로운 코드를 추가하는 방식으로 시스템을 확장할 수 있어야 한다는 원칙.
소프트웨어의 유지보수성을 높이고 시스템을 더 유연하게 만들어준다.
장점
- 기존 코드를 수정하지 않고 새로운 코드를 추가하여 확장할 수 있다.
- 중복 코드를 방지하고 효율성을 높일 수 있다.
- 하나의 책임만 가지기 때문에 가독성과 유지보수성이 좋다.
단점
- 과도하게 사용하면 잘못된 순서로 조합되는 문제가 발생할 수 있다.
- 객체의 수가 증가하여 메모리 및 성능 저하가 일어날 수 있다.
디자인 원칙
클래스 확장에는 열려 있어야 하지만, 변경에는 닫혀 있어야 한다.
기존 코드를 건드리지 않고 확장으로 새로운 행동을 추가하는 것.
'개발 도서' 카테고리의 다른 글
디자인 패턴 - 커맨드 패턴 (0) | 2023.12.09 |
---|---|
디자인 패턴 - 싱글톤 패턴 (0) | 2023.12.04 |
디자인 패턴 - 팩토리 메서드 패턴, 추상 팩토리 패턴 (1) | 2023.12.02 |
디자인 패턴 - 옵저버 패턴 (1) | 2023.11.23 |
헤드 퍼스트 디자인 패턴 - 1장 전략패턴 (1) | 2023.11.20 |