기타/디자인패턴
[Design Pattern] Decorator Pattern
[dev] hiro
2024. 5. 28. 08:54
해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)
카페 프로그램 개발
- 음료수가 많으니 추상 클래스에서 Beverage 클래스 정의
- 더 자세한 음료는 서브 클래스에서 구현
문제점
- 인기가 증가하면서 메뉴가 너무 많이 증가하고, 모든 메뉴가 Beverage를 상속함
- 첨가물도 있으니 첨가물이나 가격변동에 따른 대비가 어려움
해결책
- 인스턴스 변수와 슈퍼 클래스 상속으로 관리
문제점
- 첨가물의 가격이 바뀔때마다 코드 수정
- 새로운 음료가 출시될 경우, 특정 첨가물이 들어가면 안되는 음료가 있을 수 있음.
- 같은 첨가물을 두번 주문하는 경우
고찰
- 상속만으로는 무조건 유연하거나 관리하기 좋은 디자인은 아님
- 구성과 위임으로 실행 중에 행동을 상속하는 방법이 있음
- 서브 클래스로 행동을 상속받으면 컴파일 타임에 결정됨.
- 구성으로 객체의 행동을 확장하면 실행 중에 동적으로 할당가능.
💡 OCP(Open-Closed Principle)
클래스 확장에는 열려있지만 변경에는 닫혀있다.
Decorator Pattern
이전까지는 클래스가 너무 많아지거나 적합하지 않은 기능을 추가해야했음.
첨가물 == 음료의 장식(decorate)으로 생각
방법
- DarkRoast 객체 생성
- Mocha 객체로 장식
- Whip 객체로 장식
- cost() 메서드 호출. 첨가물의 가격을 계산하는 일은 객체에게 위임.
정리
- 데코레이터의 슈퍼 클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 동일(Beverage)
- 한 객체(음료)를 여러개의 데코레이터(샷, 우유, 모카...)로 감싸기
- 데코레이터는 행동을 위임하는것 말고도 다른 추가 메서드 가능
- 실행중에 데코레이터 적용 가능
💡 Decorator Pattern
객체에 추가 요소를 동적으로 더할 수 있음. 서브 클래스를 만들때보다 유연하게 기능확장 가능.
고찰
- CondimentDecorator에서 Beverage 클래스를 확장 ⇒ 상속아닌가?
- 상속맞지만 행동을 물려받는게 아님
- 이 구조로 실행 중에 동적으로 결정 가능
Java IO 클래스
데코레이터 패턴을 적용한 예
예시
public abstract class Beverage {
enum SIZE {
TALL, GRANDE, VENTI
}
String description = "아무것도 없음";
SIZE size = SIZE.TALL;
public String getDescription() {
return description;
}
public SIZE getSize() {
return size;
}
public void setSize(SIZE size) {
this.size = size;
}
abstract double cost();
}
public abstract class CondimentDecorator extends Beverage {
Beverage beverage;
public abstract String getDescription();
}
public class DarkRoast extends Beverage {
private double cost;
public DarkRoast() {
description = "다크 로스트 커피";
}
double cost() {
if (getSize() == SIZE.TALL) {
cost += 0.99;
} else if (getSize() == SIZE.GRANDE) {
cost += 1.49;
} else {
cost += 1.99;
}
return cost;
}
}
public class Mocha extends CondimentDecorator {
public Mocha(Beverage b) {
this.beverage = b;
}
public String getDescription() {
return beverage.getDescription() + "모카";
}
public double cost() {
return beverage.cost() + 0.2;
}
}
public class Whip extends CondimentDecorator {
public Whip(Beverage b) {
description = "휘핑";
this.beverage = b;
}
public String getDescription() {
return beverage.getDescription() + "휘핑";
}
public double cost() {
return beverage.cost() + 0.4;
}
}
public class StarbuzzCoffee {
public static void main(String[] args) {
Beverage espresso = new Espresso();
System.out.println("espresso: " + espresso.getDescription() + " cost: " + espresso.cost());
Beverage darkRoast = new DarkRoast();
darkRoast = new Mocha(darkRoast);
darkRoast = new Mocha(darkRoast);
darkRoast = new Whip(darkRoast);
System.out.println("Dark double Mocha whipping: " + darkRoast.getDescription() + " cost: " + darkRoast.cost());
Beverage sameB = new Whip(new Mocha(new Mocha(new DarkRoast())));
System.out.println("Dark double Mocha whipping: " + sameB.getDescription() + " cost: " + sameB.cost() + "\t" + sameB.getClass());
// SIZE = GRANDE;
Beverage darkRoastGrande = new DarkRoast();
darkRoastGrande.setSize(Beverage.SIZE.GRANDE);
darkRoastGrande = new Mocha(darkRoastGrande);
darkRoastGrande = new Mocha(darkRoastGrande);
darkRoastGrande = new Whip(darkRoastGrande);
System.out.println("Dark Grande double Mocha whipping: " + darkRoastGrande.getDescription() + " cost: " + darkRoastGrande.cost());
}
}