* 본 포스팅은 한빛미디어의 헤드퍼스트 디자인 패턴을 공부한 내용을 정리한 글입니다.
저의 부족한 생각과 주관으로 틀린 내용이 있을 수 있으니,
자세한 내용이 궁금하시다면 해당 책을 읽어보시길 추천드립니다.
1. 목표 : 스타비즈 주문시스템
초대형 커피전문점 스타버즈의 주문시스템 리팩토링
단기간에 폭팔적으로 성장한 스타버즈 커피전문점은 시스템이 아직도 초창기 조그마한 까페에 머물러 있습니다.
우리는 이제, 스타버즈의 주문 시스템을 현재 수준에 맞게 리팩토링 해야 합니다.
현재 주문 시스템은 규모에 걸맞지 않게 허술합니다.
초대형 커피 전문점인 만큼 손님의 기호에 맞게 다양한 커스텀이 가능한데,
이대로라면 Becerage를 상속받는 클래스들이 옵션의 수만큼 생성되게 됩니다. 수백개 수천개가요!
관리가 매우 힘들겁니다.
2. 서브클래스를 사용해 리팩토링
우선 옵션클래스들을 줄이는게 최우선으로 보이니 슈퍼클래스를 상속받는 서브클래스를 사용해 보겠습니다.
옵션이 모두 Beverage로 옮겼고, 음료들은 서브클래스를 통해 구현한 모습입니다.
클래스가 획기적으로 줄었지만 큰 문제가 존재합니다.
서브클래스를 만드는 방식은 컴파일시 행동이 결정되므로, 서브클래스가 사용하지 않는 행동도 상속받게 됩니다.
즉, 이대로라면 손님은 뜨거운 커피에 타피오카 펄 옵션을 넣어서 주문할수도 있습니다🙄
따라서, 우리는 OCP원칙이 필요합니다.
💡 OCP(Open-Closed Principle)
클래스는 확장에는 열려있어야 하지만 변경에는 닫혀 있어야 한다.
기존 코드를 그대로 두고, 확장을 통해 행동을 추가하게끔 작성해야 한다는 원칙입니다.
새로운 기능을 추가할 때 변화하는 환경에 잘 적응하는 유연하고 튼튼한 디자인을 만들 수 있습니다.
이번 목표에서 효과적으로 OCP를 준수할 수 있게 하는 패턴인 테코레이션 패턴을 사용해 봅시다!
3. 데코레이션 패턴이란?
💡 데코레이터(Decorator) 패턴
객체의 추가요소를 동적으로 더할수 있어 유연하게 기능을 확장하게 합니다.
- 데코레이터는 자신이 장식하는 객체에게 행동을 위임하거나 추가작업을 수행할 수 있습니다.
- 데코레이터의 슈퍼클래스는 자신이 장식하는 객체의 슈퍼클래스와 같습니다. 그래서, 원래 객체가 들어갈 자리에 데코레이터 객체를 넣어도 됩니다.
- 한 객체를 여러개의 데코레이터로 감쌀 수 있습니다.
- 객체는 언제든지 감쌀 수 있으므로, 동적으로 적용할 수 있습니다.
역시 글로는 감이 안잡히실거예요. 다음 그림으로 보겠습니다!
DarkRoast객체에 Mochar를 감싸고, 또 이들을 Whip이 감싼 모습입니다.
사용자가 가격을 물어보면, Whip → mocha → DarkRoast의 순서로 cost()를 호출합니다. 그리고,
0.99(DarkRoast) + .020(mocha) + 0.10(Whip)의 순으로 호출된 금액에 본인의 금액을 더해서
사용자에게 응답하게 됩니다.
그럼 클래스 구성도를 통해 좀 더 자세히 알아보겠습니다.
위에서 설명한대로 Component를 Decorator가 구현하며 Component 객체를 갖고 있습니다.
이를 이용해 ConcreteDecprator들이 ConcreteCoponent 를 주입받음으로써 감쌀 수 있습니다.
ConcreteDecprator는 메서드의 추가, 확장이 가능하며 상태를 추가할수도 있습니다.
4. 데코레이션 패턴을 사용해 주문시스템 설계하기
이제 위의 내용을 기반으로 스타버즈의 Beverage에 새로운 프레임워크를 적용해 보겠습니다.
상속보다 유연해서 데코레이터 패턴을 사용한다고 했습니다만,
결국 CodimentDecorator는 Beverage를 상속받아서 확장하고 있습니다.
이렇다면 어차피 상속을 사용해서 구현한게 아닌가 싶을 수 있습니다!
데코레이터의 상속이 일반적인 상속과 다른 점은 다음과 같습니다.
- 상속을 통해 물려받는게 아닌 형식을 맞춘다.
- 형식을 맞추면 유연하게 동적으로 사용할 수 있다.
- 새로운 행동이나 상태는 구상클래스에서 추가한다.
5. 데코레이터를 사용해 주문시스템 구현하기
설계를 마쳤으니, 이제 개발을 시작해 볼까요?
Beverage 구현 (추상 구성요소)
public abstract class Beverage {
String description = "제목 없음";
public String getDescription() {
return description;
}
public abstract double cost();
}
CondimentDecorator 구현(추상 데코레이터)
// Beverage 객체를 감쌀 수 있도록 Beverage 클래스를 확장
public abstract class CondimentDecorator extends Beverage {
Beverage beverage;
public abstract String getDescription();
}
Espresso 구현(구상 구성요소)
public class Espresso extends Beverage {
public Espresso() {
description = "에스프레소";
}
public double cost() {
return 1.99;
}
}
Mocha 구현(구상 데코레이터)
public class Mocha extends CondimentDecorator {
// 인스턴스 변수를 감싸고자 하는 객체를 받음
public Mocha(Beverage beverage) {
// 감싸고자 하는 음료를 저장하는 인스턴스 변수
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", 모카";
}
public double cost() {
return beverage.cost() + .20;
}
}
주문시스템 테스트코드
public class StarbuzzCoffee {
public static void main(String args[]) {
Beverage beverage = new Espresso();
Beverage beverage2 = new Espresso();
beverage = new Mocha(beverage); // Mocha로 감싼다
beverage = new Mocha(beverage); // Mocha로 감싼다
beverage = new Whip(beverage); // Whip으로 감싼다
System.out.println(beverage.getDescription() + " $" + beverage.cost());
// 에스프레소 커피 $1.99
System.out.println(beverage.2getDescription() + " $" + beverage.cost());
// 에스프레소 커피, 모카, 모카, 휘핑크림 $2.49
}
}
6. 정리
데코레이터 패턴의 특징
1. 구상 구성 요소를 감싸 주는 데코레이터를 사용한다
2. 데코레이터 클래스의 형식은 그 클래스가 감싸는 클래스 형식을 반영한다.
3. 데코레이터는 감싸고 있는 구성요소의 메소드 호출결과에 새로운 기능을 더해 행동을 확장한다.
데코레이터 패턴의 장점
OCP를 준수하는, 확장엔 열려있고 변경엔 닫힌 멋진 코드를 짤 수 있습니다!
데코레티어 패턴의 단점
- 클래스가 많아진다
- 클래스의 구성을 파악하기 힘들다
- 구성 요소의 초기화 코드가 복잡해진다 ! 팩토리 및 빌더 패턴으로 개선 가능
ps) 책에 나오는 추가 예제로 java i/o의 데코레이터 패턴이 있습니다
적용해보기 굉장히 좋은 예제이니 한번, 책을 구매하셔서 보시는걸 추천드립니다!
'개발공부 > Code design' 카테고리의 다른 글
헤드퍼스트 디자인 패턴 04.팩토리(Factory) 패턴 - 팩토리 메서드 (0) | 2022.09.25 |
---|---|
헤드퍼스트 디자인 패턴 04.팩토리(Factory) 패턴 - 심플 팩토리 (0) | 2022.09.25 |
헤드퍼스트 디자인 패턴 2장. 옵저버(Observer)패턴 (0) | 2022.07.17 |
헤드퍼스트 디자인 패턴 1장. 전략(Strategy)패턴 (1) | 2022.07.17 |
MVC 패턴 (0) | 2021.06.02 |