Beverage는 음료를 나타내는 추상 클래스이며 매장에서 판매되는 모든 음료는 이 클래스의 서브 클래스가 된다.
이중 cost는 추상메서드로 서브 클래스에서 이 메서드를 구현해야 한다.
문제점은 다음과 같다.
음료를 주문할 때 우유나, 모카, 두유 등 다양한 옵션을 추가하는데
이때마다 옵션에 따라 Beverage 클래스를 새로 만들어야 하는가?
또한 각 첨가물의 가격이 변한다면(예를 들어 우유의 가격이 변한다면) 우유를 포함하고 있는 음료들을 하나씩 찾아서 가격을 변경해주어야 하는가?
옵션에 따라 Beverage 클래스가 너무 많이 생긴다
클래스 수를 줄이는 방법을 생각해보자
하지만, 구성으로 객체의 행동을 확장하면 실행 중에 동적으로 행동을 변경할 수 있다.
객체룰 행동을 동적으로 변경할 수 있는 것의 장점은 다음과 같다.
기존 코드를 건드리지 않고 확장으로 새로운 행동을 추가할 수 있다.
OCP(Open Closed Principal)
클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다.
특정 음료에서 시작해 첨가물로 그 음료를 장식(decorate) 해보자
에를 들어 어떤 고객이 모카와 휘핑 크림을 추가한 다크 로스트 커피를 주문한다면 다음같이 데코레이터 패턴을 적용할 수 있다.
이때 객체를 어떻게 장식할 수 있을까?
또한 어떤 식으로 각 객체에게 가격을 계산하는 책임을 위임할 수 있을까?
데코레이터 패턴이란?
객체에 추가 요소를 동적으로 저할 수 있다. 따라서 데코레이터를 사용하면 서브 클래스를 만들 때보다 훨씬 더 유연하게 기능을 확장할 수 있다.
1. 구상 구성 요소를 감싸 주는 데코레이터를 사용한다
2. 데코레이터 클래스의 형식은 그 클래스가 감싸는 클래스 형식을 반영한다.
3. 데코레이터는 감싸고 있는 구성요소의 메소드 호출결과에 새로운 기능을 더해 행동을 확장한다.
OCP를 준수하는, 확장엔 열려있고 변경엔 닫힌 멋진 코드를 짤 수 있습니다!
클래스가 많아진다
클래스의 구성을 파악하기 힘들다
구성 요소의 초기화 코드가 복잡해진다 ! 팩토리 및 빌더 패턴으로 개선 가능
추상 구성요소
public abstract class Beverage {
String description = "제목 없음";
public String getDescription() {
return description;
}
public abstract double cost();
}
추상 데코레이터
// Beverage 객체를 감쌀 수 있도록 Beverage 클래스를 확장
public abstract class CondimentDecorator extends Beverage {
Beverage beverage;
public abstract String getDescription();
}
구상 구성요소
public class Espresso extends Beverage {
public Espresso() {
description = "에스프레소";
}
public double cost() {
return 1.99;
}
}
구상 데코레이터
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
}
}