데코레이터 패턴이란
✨ 기존 객체에 기능(책임) 추가하는 패턴이다. 새 Decorator 클래스를 만들어 상속과 컴포지션으로 구현한다.
기존 객체(ComponemtImpl)에 새로운 기능을 추가하고 싶은 경우,
- 데코레이터 클래스(Decorator)를 생성한다.
- 데코레이터 클래스를 새로운 기능이 구현된 구체 클래스(DecoratorImpl)가 상속받는다.
- 클라이언트단에서 부가 기능객체로 기존 객체를 감싼다.
- Copoment x = new DecoratorImpl1(new ComponentImpl)
이 패턴을 사용하여, 기존 객체에 새 기능을 추가하였다.
데코레이터 패턴 사용하는 이유?
- 기존 클래스의 코드를 변경하지 않으면서 새 기능을 추가할 수 있다. 이 패턴은 기존 객체에 새로운 기능을 추가하고 싶을 때 사용한다. 만약 기존 클래스에 새 메소드를 추가해줄 수도 있다. 하지만, 이는 기존 코드를 변경하게 되므로 복잡해진다. 반면 이 패턴은 기존 객체를 어댑터에 주입하는 방식이므로 기존 클래스의 코드를 변경하지 않으면서 기능을 추가할 수 있다(위 예시에서 ComponentImpl 클래스 코드를 변경하지 않고 해당 객체에 기능을 추가하였다).
- 동적으로 기능 추가할 수 있다. 클라이언트 코드에서 새 기능을 추가하므로, 런타임시에(동적으로) 기능 추가를 할 수 있다.
따라서, 데코레이터 패턴은 여러 기능들이 계속 추가되어야 하는 경우, 여러 기능들을 조합해서 사용하는 경우에 사용하는 게 좋다.
예시
- 기존 객체 클래스 : 새 기능을 추가하고 싶지만, 이 클래스의 코드는 변경하고 싶지 않은 상황
public interface ChristmasTree {
Stringdecorate();
}
// 기존 객체
public class ChristmasTreeImpl implements ChristmasTree {
@Override
public String decorate() {
return "Christmas tree";
}
}
- 데코레이터 추상 클래스 : 이를 상속받는 구체 클래스에 새 기능이 담겨있다.
public abstract class TreeDecorator implements ChristmasTree {
// 컴포지션
private ChristmasTree tree;
public TreeDecorator(ChristmasTree tree) {
this.tree = tree;
}
@Override
public String decorate() {
return tree.decorate();
}
}
- 데코레이터 구체 클래스 : 데코레이터 추상 클래스를 상속받아서 새로운 기능을 구현한 구체 클래스
// 구체 클래스 1
public class BubbleLights extends TreeDecorator {
public BubbleLights(ChristmasTree tree) {
super(tree);
}
// 새 기능 추가
public String decorate() {
return super.decorate() + decorateWithBubbleLights();
}
private String decorateWithBubbleLights() {
return " with Bubble Lights";
}
}
// 구체 클래스 2
public class Garland extends TreeDecorator {
public Garland(ChristmasTree tree) {
super(tree);
}
public String decorate() {
return super.decorate() + decorateWithGarland();
}
private String decorateWithGarland() {
return " with Garland";
}
}
- 클라이언트 코드 : 부가 기능 객체로 기존 객체를 감싼다. 이를 통해 기존 객체에 새로운 기능을 추가/조합할 수 있다.
@Test
public void whenDecoratorsInjectedAtRuntime_thenConfigSuccess() {
ChristmasTree tree1 = new Garland(new ChristmasTreeImpl());
tree1.decorate(); // "Christmas tree with Garland"
ChristmasTree tree2 = new BubbleLights(
new Garland(new Garland(new ChristmasTreeImpl())));
tree2.decorate(); // "Christmas tree with Garland with Garland with Bubble Lights"
}
정리하면
데코레이터 패턴은 기존 객체에 새 기능을 추가할 때 사용한다. 기존 클래스의 코드 변경 없이, 동적으로 새 기능을 추가할 수 있다. 따라서 계속 기능을 추가해주어야하는 상황 혹은 여러 기능을 조합해주어야 하는 상황에서 사용하는 게 좋다.
Reference
- https://www.baeldung.com/java-decorator-pattern
- https://coding-factory.tistory.com/713