What is 데코레이터?
- 데코레이터 패턴 : 주어진 상황 및 용도에 따라 어떤 객체에 책임을 덧붙이는 패턴, 기능 확장이 필요할 때 서브클래싱 대신 쓸 수 있는 유연한 대안이다
이게 무슨 말일까요?
우리가 기능을 확장할 때 다양한 방법으로 확장할 수 있는데,
객체지향적인 언어에서는 상속을 통해서 기능을 확장할 수가 있어요
하지만 필연적인 상속이 정말 좋은 방법일까요?
상속은 객체지향을 위한 강력한 무기지만, 동시의 부모클래스와의 강력한 의존성을 가지게 되죠
이러한 상속을 대신하여 해결할 수 있는 방법은 다른 클래스에 합성시키는 것입니다
다이어그램을 보면서 말씀드릴게요~!
Component라는 구성요소 클래스가 있죠?
Decorator클래스는 Component를 상속함과 동시에 합성을 하여 멤버변수로 Component를 가지고 있어요
이렇게 하면 Decorator클래스는 기존의 있던 Component의 기능을 가지고, Decorator의 입맛에 맞게 기능을 확장할 수 있게 됩니다!
조금 더 쉽게 예시로 다가가볼게요~
When 데코레이터?
피자가게 라는 도메인을 예로 들어볼까요?
피자가게는 기본적으로 피자를 팔겠죠?
하지만 피자(Component)의 개수가 1개던가요?
치즈피자(ConcreteComponent), 페퍼로니피자(ConcreteComponent), 하와이안피자(ConcreteComponent).... etc
더 많겠죠?
이렇게 팔다가.. 소비자들의 입맛이 더욱 까다로워져서
기존에 있던 피자에서 소비자들의 입맛대로 토핑을 얹는다고 생각해볼까요?
그러면 토핑을 하나 얹을때마다 치즈피자에서 상속, 페퍼로니 피자에서 상속... 하게되면
객체들이 너무 너무너무 많아질겁니다ㅠㅠ
이때 해결하는 것이 바로 데코레이터 패턴입니다
소비자의 커스텀 피자(Decorator)라는 객체를 만들어서
토마토를 추가한 치즈피자(Concrete Decorator), 불고기를 추가한 페퍼로니피자(Concrete Decortator)를 만드는 겁니다!
그렇게 되면 조금 더 쉽게 기능을 확장하면서, 시스템이 복잡해지지 않겠죠?
How to 데코레이터
그러면 실제 구현을 해볼께요!
먼저 피자를 만듭니다
package designpattern.decorator;
abstract class Pizza {
protected String description = "Original Pizza";
public String getDescription() {
return description;
}
public abstract int getCost();
}
1. Pizza 클래스
추상화된 일반 Pizza 클래스를 만들어서 설명과 가격을 반환하는 메서드를 작성합니다!
그러면 조금 더 구체화된 피자 메뉴들을 만들어볼까요?
package designpattern.decorator;
class CheesePizza extends Pizza {
public CheesePizza() {
this.description = "CheesePizza";
}
@Override
public int getCost() {
return 10_000;
}
}
package designpattern.decorator;
class CombinationPizza extends Pizza {
public CombinationPizza() {
this.description = "CombinationPizza";
}
@Override
public int getCost() {
return 12_000;
}
}
2. 실체화된 Pizza 클래스들
실제 치즈피자와 콤비네이션피자를 실체화환 객체들을 만들었어요!
가격과 설명에 대한 정보를 내포하고 있습니다!
그러면 이제 토핑(Decorator)들을 만들어볼까요?
package designpattern.decorator;
abstract class ToppingDecorator extends Pizza {
protected Pizza pizza;
ToppingDecorator(final Pizza pizza) {
this.pizza = pizza;
}
public abstract String getDescription();
}
3. 데코레이터(토핑) 객체
클래스 다이어그램에 나타난대로 pizza 객체를 합성시킨 데코레이터 클래스입니다
데코레이터를 실체화한 객체도 만들어봅시다!
package designpattern.decorator;
class FreshTomato extends ToppingDecorator {
public FreshTomato(final Pizza pizza) {
super(pizza);
}
@Override
public String getDescription() {
return pizza.description + " with FreshTomato";
}
@Override
public int getCost() {
return 300 + pizza.getCost();
}
}
package designpattern.decorator;
class Olive extends ToppingDecorator {
public Olive(final Pizza pizza) {
super(pizza);
}
@Override
public String getDescription() {
return pizza.description + " with Olive";
}
@Override
public int getCost() {
return 400 + pizza.getCost();
}
}
4. 토핑들을 실체화한 데코레이터 객체
토핑들을 얹고, pizza에 대한 가격과 설명이 달라지는 모습을 볼 수 있습니다!!!
한번 테스트하러 가볼까요?
package designpattern.decorator;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class ToppingDecoratorTest {
@Test
@DisplayName("치즈피자에 토마토 토핑이 제대로 올라가는지 테스트")
void FreshTomato_with_CheesePizza() {
/* Given */
Pizza cheesePizzaWithFreshTomato = new FreshTomato(new CheesePizza());
/* Then */
assertThat(cheesePizzaWithFreshTomato.getDescription()).isEqualTo("CheesePizza with FreshTomato");
assertThat(cheesePizzaWithFreshTomato.getCost()).isEqualTo(10_300);
}
@Test
@DisplayName("콤비네이션 피자에 올리브 토핑이 제대로 올라가는지 테스트")
void Olive_with_CombinationPizza() {
/* Given */
Pizza combinationPizzaWithOlive = new Olive(new CombinationPizza());
/* Then */
assertThat(combinationPizzaWithOlive.getDescription()).isEqualTo("CombinationPizza with Olive");
assertThat(combinationPizzaWithOlive.getCost()).isEqualTo(12_400);
}
}
5. 실제 테스트 코드
위 테스트 코드를 돌려보면...
제가 작성한 코드의 다이어그램을 보면서 끝낼게요!
데코레이터 장단점
장점
- 데코레이터 패턴은 런타임 시에 기능을 확장하는게 가능하다!
- 상속 대신 합성을 통해서 의존성을 느슨하게 하고, 데코레이터 객체의 기능확장에 대해서 유연하다!
- 데코레이터 객체를 통해서 기능을 점진적으로 확장할 수 있다!
단점
- 데코레이터 자체 객체는 원래 Component를 인스턴스화 해야하기 때문에 설계 과정이 복잡하다
- 데코레이터를 추적하거나 디버깅해야할 때 많이 복잡하고 힘들 수 있다
모자란 글이지만.. 다들 잘 이해가 되셨을란지 모르겠어요!
한번, 실무에 적용하면서 설계해보시면 좋을 것 같아요!ㅎㅎ
참고
Decorator Pattern - GeeksforGeeks
Decorator Pattern - Wikipedia
'Developer > Design Pattern' 카테고리의 다른 글
Template Callback Pattern - 템플릿 콜백 패턴 (0) | 2020.02.23 |
---|---|
Proxy Pattern - 프록시 패턴 (0) | 2020.01.25 |
Bridge Pattern - 브릿지 패턴 (0) | 2020.01.05 |
Composite Pattern - 컴포지트 패턴 (0) | 2019.11.06 |
Command Pattern - 커맨드 패턴 (2) | 2019.10.19 |