취업과 기본기 튼튼/빽 투더 기본기
[디자인 패턴 8편] 구조 패턴, 데코레이터(Decorator)
흠시
2020. 2. 22. 01:40
1. 개념
데코레이터 패턴은 기본 객체에
추가적인 기능을 동적으로 유연하게 첨가하는 패턴이다.
1.1. 장점
- 객체에 동적으로 기능 추가가 간단하게 가능하다.
1.2. 단점
- 자잘한 데코레이터 클래스들이 계속 추가되어 클래스가 많아질 수 있다.
- 겹겹이 애워싸고 있기 때문에 객체의 정체를 알기 힘들고 복잡해질 수 있다.
1.3. 활용 상황
- 객체가 상황에 따라 다양한 기능이 추가되거나 삭제되어야 할 때.
2. 구조
- Component
- ConcreteComponent 과 Decorator 가 구현할 인터페이스다.
두 객체를 동등하게 다루기 위해 존재함
- ConcreteComponent 과 Decorator 가 구현할 인터페이스다.
- ConcreteComponent
- Decorate 를 받을 객체다.
즉, 기능 추가를 받을 기본 객체
- Decorate 를 받을 객체다.
- Decorator
- Decorate 를 할 객체의 추상 클래스다.
즉, 기능 추가를 할 객체는 이 객체를 상속받는다.
- Decorate 를 할 객체의 추상 클래스다.
- ConcreteDecorator
- Decorator 를 상속받아 구현할 다양한 기능 객체이다.
이 기능들은 ConcreteComponent 에 추가되기 위해 만들어 진다.
- Decorator 를 상속받아 구현할 다양한 기능 객체이다.
3. 구현
크리스마스 트리에 다양한 장식을 하고싶다고 해보자.
기본 크리스마스 트리가 있고, 장식으로는 깜빡이는 불이나, 꽃들을 생각해볼 수 있다.
Component
먼저 크리스마스 트리와 장식을 각각 추상화 해야할 것이다.
이들을 먼저 다음과 같은 인터페이스로 공통 추상화 하자.
interface ChristmasTree {
String decorate();
}
ConcreteComponent
먼저 기본 크리스마스 트리를 다음과 같이 구체화할 수 있다.
class DefaultChristmasTree implements ChristmasTree {
@Override
public String decorate() {
return "Christmas tree";
}
}
그렇다. 그냥 아무 장식이 없는 그냥 나무다.
Decorator
다음으로, 트리에 올라갈 장식은 다음과 같이 추상화할 수 있다.
abstract class TreeDecorator implements ChristmasTree {
private ChristmasTree christmasTree;
public TreeDecorator(ChristmasTree christmasTree) {
this.christmasTree = christmasTree;
}
@Override
public String decorate() {
return christmasTree.decorate();
}
}
구체적인 장식들, 즉 장식용 불이나 꽃은 이 추상 클래스를 상속받아 구현하면 된다.
ConcreteDecorator
먼저 장식으로 두를 불을 먼저 구체화 해보자.
class Lights extends TreeDecorator {
public Lights(ChristmasTree christmasTree) {
super(christmasTree); // 여기가 포인트.
}
public String addLights() {
return " with Lights";
}
@Override
public String decorate() {
return super.decorate() + addLights(); // 여기가 포인트.
}
}
왜 이렇게 구현했는지는 나중에 클라이언트 쪽 코드를 보면 알게된다.
마찬가지로 장식용 꽃도 비슷하게 구체화 하자.
class Flowers extends TreeDecorator {
public Flowers(ChristmasTree christmasTree) {
super(christmasTree);
}
public String addFlowers() {
return " with Flowers";
}
@Override
public String decorate() {
return super.decorate() + addFlowers();
}
}
클라이언트 단
클라이언트 단에서는 다음과 같이 코드를 작성한다.
public class DecoratorDemo {
public static void main(String[] args) {
// Christmas tree 만 있음.
ChristmasTree tree = new DefaultChristmasTree();
System.out.println(tree.decorate());
// Christmas tree + Lights
ChristmasTree treeWithLights = new Lights(
new DefaultChristmasTree()
);
System.out.println(treeWithLights.decorate());
// Christmas tree + Lights + Flowers
ChristmasTree treeWithLightsAndFlowers = new Flowers(
new Lights(
new DefaultChristmasTree()
)
);
System.out.println(treeWithLightsAndFlowers.decorate());
}
}
실행하면 다음과 같은 결과를 볼 수 있다.
Christmas tree
Christmas tree with Lights
Christmas tree with Lights with Flowers
다시 클라이언트 코드부분을 살펴보면,
기본 객체인 new DefaultChristmasTree()
에 기능 추가를 new Lights(new DefaultChristmasTree());
와 같이 동적인 방식으로 하고 있다.
이게 가능한 이유는
- Decorator 객체의 생성자로 Component 를 받음으로써 Decorator 를 이어 붙일 수가 있고
super
를 통해 넘어오는 Component 의 operation(decorate()
) 을 먼저 수행하기 때문이다.
이후 추가적인 장식(기능)을 만들고 싶으면 TreeDecorator
를 상속받아 위와 같은 꼴로 또 구현하면 된다.
즉 새로운 기능을 유연하게 만들고 추가할 수 있다.