Goal
- 데로레이터 패턴에 대해 알아보고 이해한다
- 샘플코드를 통한 데코레이터 패턴을 이해한다
데코레이터 패턴 (Decorator Parttern)
객체에 추가 요소를 동적으로 더할 수 있는 패턴.
다이어그램
데코레이터 패턴을 사용하는 이유
상속을 사용할 경우 클래스가 많아지거나 일부 서브클래스에는 적합한 기능을 추가해야하는 문제가 있지만, 데코레이터 패턴을 사용하면 보다 훨씬 유연하게 기능을 확장할 수 있기 때문이다.
주문 시스템에서 생각해보기
상황 : 상속을 써서 음료 가격과 첨과물(샷, 시럽, 우유, 휘핑크림 등) 가격을 합해서 총 가격을 산출하는 방법은 좋은 방법이 아님
요구 사항 : 다크로스트에 모카, 휘핑크림을 추가한 음료
해결 방안 : 데코레이터 패턴을 사용해서 가격을 계산해보자
1. DarkRoast 객체에서 시작한다
- DarkRoast는 Beverage로부터 상속받으므로 음료의 가격을 계산하는 메소드를 가지고 있다
2. Mocha 객체로 장식한다
- 고객이 모카를 주문했으니 Mocha를 만들고 그 객체로 DarkRoast객체를 감싼다
- Mocha 객체는 데코레이터이다. 객체의 형식은 객체가 장식하고 있는 객체를 반영하는데, 이 경우에는 Beverage가 된다.
- 따라서 Mocha에도 cost() 메소드 - 음료 가격 계산 메소드가 있꼬, Mocha가 감싸고 있는것도 Beverage 객체로 간주 할 수 있다 => 다형성
3. Whip 객체로 장식한다
- Whip도 추가 했으니 Whip 객체를 만들고 Mocha를 감싼다
- 역시나 DarkRoast의 형식을 반영하여 cost() 메소드를 가지고 있다
4. cost() 메소드를 호출한다. 이때 첨가물의 가격을 계산하는 일은 해당 객체에 위임한다
- 가격을 구할 때는 가장 바깥쪽에 있는 데코레이터인 Whip의 cost()를 호출하면된다. 그러면 Whip은 그 객체가 장식하고 있는 객체에게 계산을 위임한다. 가격이 구해지고 나면, 계산된 가격에 휘핑크림의 가격을 더한 다음 그 결과값을 리턴한다
- Whip의 cost() 호출 => Whip은 Mocha의 cost() 호출 => Mocha는 DarkRoast의 cost() 호출=> DarkRoast는 99센트 리턴=> Mocha는 99센트에 + 20센트 더해서 1.19달러 리턴 => Whip은 Mocha로부터 받은 가격에 10센트를 더해 1.29달러 리턴
데코레이터 패턴 특징
- 데코레이터의 슈퍼클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 같다
- 한 객체를 여러개의 데코레이터로 감쌀 수 있다
- 데코레이터는 자신이 감싸고 있는 객체와 같은 슈퍼클래스를 가지고 있기에 원래 객체(싸여 있는 객체)가 들어갈 자리에 데코레이터 객체를 넣어도 된다
- 데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 일 말고도 추가 작업을 수행할 수 있다
- 객체는 언제든지 감쌀 수 있으므로 실행 중에 필요한 데코레이터를 마음대로 적용할 수 있다
데코레이터 패턴을 활용하여 커피 주문 시스템 코드 만들기
음료 클래스
public abstract class Beverage {
String description = "제목 없음";
public String getDescription(){
return description;
}
public abstract double cost();
}
getDescription은 이미 구현되어 있지만 cost()는 서브클래스에서 구현해야함
첨가물 클래스
public abstract class CondimentDecorator extends Beverage{
Beverage beverage;
public abstract String getDescription();
}
각 데코레이터가 감쌀 음료를 나타내는 Beverage객체를 여기서 지정하고, 음료를 지정할 때는 데코레이터에서 어떤 음료든 감쌀 수 있도록 Beverage 슈퍼클래스 유형을 사용한다
또한 첨가물 데코레이터에 getDescription() 메소드를 새로 구현하도록 만들도록 추상메소드로 구현한다.
음료 코드 구현하기
public class Espresso extends Beverage{
public Espresso() {
description = "에스프레소";
}
@Override
public double cost() {
return 1.99;
}
}
클래스 생성자 부분에서 description이라는 변수값을 설정하며, 이 인스턴스 변수는 Bevarage로부터 상속받음
이 클래스 첨가물 가격만 리턴한다
public class HouseBlend extends Beverage{
public HouseBlend() {
description = "하우스 블렌드 커피";
}
@Override
public double cost() {
return .89;
}
}
첨가물 코드 구현하기
public class Mocha extends CondimentDecorator{
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() +", 모카";
}
@Override
public double cost() {
return beverage.cost()+.20;
}
}
- 모카 인스턴스에는 Beverage의 레퍼런스가 들어있고 다음과 같이 두가지가 필요하다
- 감싸고자 하는 음료를 저장하는 인스턴스 변수
- 인스턴스 변수를 감싸고자 하는 객체로 설정하는 생성자
- 설명에는 첨가물 아이템을 추가한다. 먼저 장식하고 있는 객체에 작업을 위힘하고 다음 결과에 모카를 더한 값을 리턴한다
- 음료 가격에 모카를 추가한 가격을 계산해야한다. 우선 장식하고 있는 객체에 가격을 구하는 작업을 위임해서 음료값을 구하고 모카 값을 더하여 그 합을 리턴한다
커피 주문 시스템 코드 테스트
public class StarbucksCoffee {
public static void main(String[] args){
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()+" $"+beverage.cost());
Beverage beverage2 = new HouseBlend();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()+" $"+beverage2.cost());
}
}
결과
에스프레소 $1.99
하우스 블렌드 커피, 모카, 모카, 휘핑크림 $1.49
'JAVA > 디자인 패턴' 카테고리의 다른 글
[Simple Factory] 간단한 팩토리 (0) | 2023.07.07 |
---|---|
[Strategy Pattern] 전략 패턴 (1) | 2023.07.06 |
[Proxy Pattern] 프록시 패턴 - 보호 프록시 (0) | 2023.07.04 |
[Proxy Pattern] 프록시 패턴 - 가상 프록시 (0) | 2023.07.03 |
[Proxy Pattern] 프록시 패턴 - 원격 프록시 (0) | 2023.07.03 |