728x90
장식자 패턴(Decorator Pattern)이란?
- 객체에 동적으로 새로운 행위를 추가할 수 있도록 해주는 구조 패턴이다.
- 클래스의 책임을 실행 시간에 코드를 수정하지 않고 바꾸고 싶을 때 사용한다.
- 전략 패턴도 이 기능이 가능하다.
- 전략 패턴은 전략 객체를 바꿔 실행시간에 확장한다.
- 장식자 패턴은 한 객체를 다른 객체로 포장하여 책임을 추가/변경한다
- 장식한 개체는 여전히 원래 객체와 같은 타입이다. -> is-a
- 한 객체를 여러 번 포장할 수 있고, 보통 포장하는 순서가 중요하지 않다.
- 포장하는 순서가 중요할 시 제한을 따로 둬야한다.
- is-a와 has-a를 모두 사용한다.
- has-a(포함 관계)를 이용해 책임을 추가/변경한다.
사용하는 이유
위와 같은 주문 시스템이 있다고 하자. 주 목적은 가격을 계산하는 것이다.
커피에는 HouseBlend, DarkRoast, Decaf, Expresso와 같은 종류가 있다.
이때, 커피 주문 시 요구사항을 추가하고 싶다고 하자.
ex) 우유 좀 더 넣어주세요, 휘핑크림은 빼주세요 등등
이처럼 추가해야하는 상황이 많아지게 된다면, 클래스가 수도 없이 많아지게 된다.(클래스 폭발, class explosion)
이러한 문제를 해결하는 방법을 알아보자.
우선 다음과 같은 방법을 생각해낼 수 있다.
위와 같이 되면 첨가물에 대한 계산은 Beverage 클래스의 cost에서 계산한다.
하지만 다음과 같은 문제점이 있다.
- 추가되는 것의 가격이 변동되면 기존 코드를 변경해야 한다.
- 새로운 첨가물이 생기면 새 메소드를 추가해야 하며, cost 메소드 또한 수정해야 한다. 즉 OCP원칙에 위배된다.
- 새로운 음료가 추가될 수 있고, 기존 첨가물이 새 음료에 적합하지 않을 수 있다. 즉 LSP, ISP 원칙에 위배된다.
- 새 음료에 적합하지 않은 것은 장식 제한을 통해 해결할 수 있다.
- 상속을 통해 행위를 상속받을 수 있지만, 상속되는 행위는 컴파일 시간에 고정되며, 모든 자식 클래스는 동일한 행위를 상속받아야 한다.
- has-a를 이용하면 객체에 행위를 실행 시간에 추가할 수 있다.
위의 문제들을 해결할 수 있게 해주는 것이 장식자 패턴(Decorator Pattern)이다.
장식자 패턴(Decorator Pattern) 구현
커피 예제의 장식자 패턴을 구현한 것을 UML로 보면 위와 같다.
장식자 패턴의 구현을 위해서는 패턴의 구성 요소를 알아야한다.
- Component(Beverage 클래스): 인터페이스 혹은 추상 클래스를 정의한다. 즉, 사용할 기본 틀이 된다.
- ConcreateComponent(DarkRoast, HouseBlend): Component를 장식할 구체적인 객체 타입이다.
- ComponentDecorator: 추상클래스 혹은 인터페이스이며, Component의 서브 클래스이다. Component와 ConcreateDecorator 사이에서 연결해주는 역할을 한다.
- ConcreateDecorator(Milk, Mocha, Whip): 확장 및 추가할 기능을 작성한다.
장식자 패턴 코드
Component(Beverage)
public abstract class Beverage{
private String description = "이름없는 음료";
public void setDescription(String description){
this.description = description;
}
public String getDescription(){
return description;
}
public abstract int cost();
}
ConcreateComponent(DarkRoast, HouseBlend)
public class DarkRoast extends Beverage {
public DarkRoast(){
setDescription("다크로스트 커피");
}
@Override
public int cost() {
return 1200;
}
}
public class HouseBlend extends Beverage {
public HouseBlend(){
setDescription("하우스블랜드 커피");
}
@Override
public int cost() {
return 1000;
}
}
CondimentDecorator
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
ConcreateDecorator(Milk, Mocha, Whip)
public class Milk extends CondimentDecorator {
private Beverage beverage;
public Milk(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+", 우유";
}
@Override
public int cost() {
return beverage.cost()+500;
}
}
public class Mocha extends CondimentDecorator {
private Beverage beverage;
public Mocha(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+", 모카";
}
@Override
public int cost() {
return beverage.cost()+200;
}
}
public class Whip extends CondimentDecorator {
private Beverage beverage;
public Whip(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+", 크림";
}
@Override
public int cost() {
return beverage.cost()+500;
}
}
적용자 패턴을 적용할 수 있는 상황
- 상속에 의한 제약사항에 구속받지 않고 다른 동일 타입 객체들에게 영향을 주지 않고 동적으로 객체에 책임을 추가하고 싶을 경우
- 시스템이 동작하면서 객체에 기능을 추가하고 제거하고 싶을 경우
- 동적으로 객체에 추가할 수 있는 서로 독립적인 다양한 기능들이 존재할 경우
장단점
- 코딩이 단순해지며, 클래스의 응집력이 높아질 수 있다.
- 상속보다 유연하게 객체에 기능 또는 상태를 추가할 수 있으며, 나중에 제거 또한 가능하다.
- 장식을 통한 제거보다는 새롭게 장식하는 방법을 주로 사용한다.
- 관련 클래스에 중복되어 있는 장식 요소를 한 곳에 정의할 수 있다.
- 클래스가 비교적 많이 정의될 수 있고, 디버깅이 힘들어질 수 있다.
- 장식자 패턴의 실제 목적은 클래스의 수를 줄이는 것이다.
728x90
'디자인 패턴 > Design Pattern' 카테고리의 다른 글
[디자인 패턴] 관찰자 패턴(Observer 패턴) (0) | 2022.10.11 |
---|---|
[디자인 패턴] 전략 패턴(Strategy Pattern) (0) | 2022.10.10 |
댓글