일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- MSA
- 연결리스트 종류
- 완전이진트리
- AVL트리
- 최대 힙
- 백준 장학금
- jpa n+1 문제
- heapq
- 점근적 표기법
- 알고리즘
- HTTP
- 강화학습
- 힙트리
- 자료구조
- Kruskal
- spring
- 연결리스트
- 백준장학금
- 멀티프로세서
- 이분탐색이란
- 운영체제
- 엔티티 그래프
- JVM
- 프로세스
- 스케줄링
- posix
- JPA
- python
- SpringSecurity
- 최소힙
- Today
- Total
KKanging
[디자인패턴] 음료시스템 예제로 배우는 Decorator Pattern 본문
음료 시스템
요구사항
음료를 주문하는 주문시스템을 생각해보자
음료에는 HosueBlend 와 DarkRoast , Decaf , Esppresso 가 있다.
구현

객체 지향적인 설계로 공통된 멤버를 묶기 위해 Beverage라는 추상 클랫로 구현체들을 상속 받게 하였다.
다음은 클라이언트 코드이며 Beverage 변수에 선택된 클래스의 객체를 주입하고 cost해주면 된다.
Beverage beverage = new Espresso();
beverage.cost();
추가 요구사항
음료에 부가 메뉴를 추가하는 것도 구현해주세요!
각 음료에 시럽을 추가할 수도 우유를 추가할 수도 있게 그리고 부가 메뉴를 추가하면 해당 재료마다 추가 비용이 발생하게 해주세요!
구현?

위와 동일한 방식으로 구현하였다.
만약 Milk가 추가된 EspressoWithMilk 라면 milk 가격과 espresso 가격이 추가된 가격이 에스프레소에 추가될 것이다.
뭐가 문제일까?
사실 뭐가 문제인지 정의하지 않아도 보기만해도 문제가 있다고 생각든다.
우선 Decafaine 의 가격이 변경되면? 4가지 클래스를 하나하나 찾아가서 변경한다.
Milk 가격이 변경되면? Milk가 들어있는 클래스 하나하나 찾아가서 변경한다.
이는 변경에 너무 취악하며 음료와 부가 메뉴에 대한 책임이 분리되지 않아서이다.
개선?

멤버 변수에 부가 메뉴 추가 여부를 구현하여 setter 를 통해서 subClass 가 추가여부를 결정한다.
public class Beverage {
protected String description;
boolean milk, soy, mocha, whip;
public float cost () {
float condimentCost = 0.0;
if (hasMilk())
condimentCost += milkCost;
if (hasSoy())
condimentCost += soyCost;
if (hasMocha())
condimentCost += mochaCost;
if (hasWhip())
condimentCost += whipCost;
return condimentCost;
}
}
Beverage는 음료 비용에 부가 메뉴 여부를 검사하여 비용을 추가한다.
Beverage beverage = new Espresso();
beverage.setIsMilk(true);
beverage.setIsSoy(true);
beverage.cost();
해결했을까? 문제점은?
위에 예제가 첫 구현보다 나아진 것은 분명하다. 하지만 유지보수 관점에서 아직 부족한데 무엇이 부족할까?
- 비용이 변경한다면?
- 부가 메뉴가 추가되거나 삭제 된다면?
- 새로운 음료가 추가될 때 해당 음료에는 기존 부가 메뉴가 적절하지 않을 수 있다.
어떻게 해결해야할까
위 예제의 문제는 Beverage라는 SuperClass 가 SubClass 의 책임과 행동을 감싼다.
SuperClass 가 아닌 모듈에서 SubClass의 책임과 행동을 동적으로 감싸는 방법이 없을까?
기능에 대한 확장은 유연하고 확장을 했을 때는 기존 코드에 수정을 최소화 해야한다(OCP)
Decorate Pattern
데코레이터 패턴이란?

데코레이터 패턴은 기존 모듈의 기능을 데코레이터들이 런타임 환경에서 확장을 하게 해준다.
멤버 변수로 데코레이터 대상을 포함하고 기존 기능을 추가한 기능을 데코레이터들이 구현한다.

데코레이터들은 모듈과 다른 데코레이터들과 같은 super type을 가진다.
데코레이터 패턴으로 구현하기

Decorator
public abstract class CodimentDecorator extends Beverage {
protected Beverage beverage;
public abstract String getDescription();
}
Concrete Decorator
public class Mocha extends CodimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return "Mocho, " + this.beverage.getDescription();
}
@Override
public double cost() {
return this.beverage.cost() + .20;
}
}
client code
public class Starbuzzcoffee {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
Beverage beverage3 = new DarkRoast();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription() + " $" + beverage3.cost());
}
}
적용 부분 및 개선된 점
Beverage를 상속받는 CodimentDecorator 라는 Decorator 추상클래스를 만들고 CodimentDecorator Association 관계로 Beverage 객체를 멤버 변수로 가진다.
ConcreteDecorator 들은 멤버 변수인 Beverage에 부가Decorator들이 기능과 책임을 추가한다.
ConcreteDecorator 객체 또한 Beverage 객체의 상속된 subClass 이므로 다른 혹은 같은 ConcreteDecorator 이 기능과 책임을 감싸는게 가능하고,
만약 ConcreteDecorator 의 종류가 추가된다고 하더라도 기존 코드 변경 없이 해당 클래스만 삭제 혹은 추가 되도록 개선되었다.
위처럼 Decorator들은 Component를 감싸서 동적으로 책임과 행동을 추가한다.
이제 부가 메뉴를 중복으로 여러번 추가하든 아니면 메뉴가 추가되거나 없어져도 기존 Beverage 코드들은 변경점이 없고 해당 메뉴 클래스만 추가 또는 삭제해주면 된다.
Decorate Pattern의 목적
목적
기존 코드의 변경 없이 객체에 새로운 책임과 동작을 유연하게 부여하거나 수정할 수 있도록 하여, 모듈의 확장성을 높이고 유지보수를 용이하게 하기 위함이다.
사용시기
- 객체의 동작이나 책임이 실행 중에 동적으로 변경되어야 하는 경우
- 모듈에 여러 부가 기능이 중첩적으로 추가되어야 하거나, 각각의 기능을 독립적으로 결합할 수 있어야 할 때
- 기능 확장이나 수정 시, 기존 코드에 대한 변경 없이 새로운 기능을 손쉽게 추가하고자 할 때
- 상속을 통한 기능 확장이 복잡하거나 적합하지 않은 상황에서, 더 많은 유연성을 제공하고자 할 때
'백엔드 > 아키텍처 & 패러다임 & 디자인 패턴' 카테고리의 다른 글
설계 원칙 , 전지적 2년차 뉴비 시점 (0) | 2025.04.07 |
---|---|
[디자인 패턴] 센서 디스플레이 예제로 배우는 Observer Pattern (4) | 2024.10.12 |
[디자인 패턴] 오리 예제로 알아보는 Strategy Pattern (0) | 2024.10.11 |
자바 개발자가 AOP를 공부 해야하는 이유 (1) | 2024.09.26 |