디자인 패턴(구)/구조 패턴

데코레이터 패턴(Decorator Pattern)이란?

공대키메라 2022. 4. 14. 00:47

1. 데코레이터 패턴

의도

객체에 동적으로 새로운 책임을 추가할 수 있게 한다.

기능을 추가하려면, 서브클래스를 생성하는 것보다 용통성이 있다.

 

즉, 기존 코드를 변경하지 않고 부가 기능을 추가하는 패턴이다. 

 

상속이 아닌 위임을 사용해서 보다 유연하게(런타임에) 부가 기능을 추가하는 것도 가능하다.

 

우리가 장식을 생각한다면 장식품을 달아주는 주체는 그 장식(데코레이터)만 추가될 뿐 주체는 여전히 동일하다. 

(예 : 크리스마스 트리에 장식품이 달려도 트리는 그대로임) 

 

이것처럼 디자인 패턴에서도 이러한 느낌으로 데코레이터 패턴을 고안한게 아닌가 생각이 든다. (내 뇌피셜)

사용 시기

  • 동적으로 또한 투명하게(transparent), 다시 말해 다른 객체에 영향을 주지 않고 개개의 객체에 새로운 책임을 추가하기 위해 사용
  • 제거될 수 있는 책임에 대해 사용
  • 실제 상속으로 서브클래스를 계속 만드는 방법이 실질적이지 못할 때 사용

구조

  • Component : 동적으로 추가할 서비스를 가질 가능성이 있는 객체에 대한 인터페이스
  • ConcreteComponent : 추가적인 서비스가 실제로 정의되어야 할 필요가 있는 객체
  • Decorator : Component객체에 대한 참조자를 관리하면서 Component에 정의된 인터페이스를 만족하도록 인터페이스를 정의 
  • ConcreteDecorator : Component에 새롭게 추가할 서비스를 실제로 구현하는 클래스 

decorator가 ConcreteComponent를 Component로 감싸고 있다. 그래서 일반적으로 Decorator pattern을 wrapper라고 부르기도 한다.

2. 구현

구현(Implementation) 동화

필자 공대 키메라는 크리스마스 트리가 좋다.

어린시절, 싼타할아버지가 선물준다고 그렇게 좋아했는데 결국 내가 받은 선물은 책이었다. (쌉노잼 진짜...)

좋은 추억을 되살리고자 트리를 사서 조립하려고 한다. 

우선 트리를 샀다.

그런데 장식품은 너무도 많다! 

그래서 우선 별장식을 달았다.

그런데 무언가 맘에 안들어서 다시 양말 장식을 달았다.

무엇을 추가했다가 뺏다가... 이것의 무한 반복을 했다.


그러면 트리를 만들기 위해 무엇을 해야할까?

우선 트리는 크리스마스에 만드니까 모든 재료들은 크리스마스 당일에 만드는 것으로 정했다.

같이 메리크리스마스를 외치면서 만드는 것이다(히히)

그리고 재료를 트리에 추가할 것이다.

Tree.java

public interface Tree {
    String marryChristmas();
}

TreeComponent를 interface로 생성했다.

 

그리고 RealTree.java에서 이를 상속해 구현체를 만들었다. 

 

구호는 트리를 만들자! "Let's make Real Tree!" 다

RealTree.java

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class RealTree implements TreeComponent {
    @Override
    public String marryChristmas() {
        log.info("Let's make Real Tree!");
        return "tree";
    }
}

트리를 만들기는 하는데 만들어지는 tree에 star를 추가할 것이다.

Star.java

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Star implements Tree {

    private Tree tree;

    public Star(Tree tree) {
        this.tree = tree;
    }

    @Override
    public String marryChristmas() {
        String result = decoComponent.marryChristmas();
        String addedTree = "star -- " + result + " -- star";
        log.info("star 꾸미기 전... {}", result);
        log.info("star 꾸민 후... {}", addedTree);
        return addedTree;
    }
}

우리는 그때 그때 마음에 드는 장식을 꺼내서 전달해주면 된다.

TreeDecoratorClient.java

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TreeDecoratorClient {

    private TreeComponent treeComponent;

    public TreeDecoratorClient(TreeComponent treeComponent) {
        this.treeComponent = treeComponent;
    }

    public String decorate(){
        log.info("TreeDecoratorClient");
        return treeComponent.marryChristmas();
    }

}

MakeTree.java

public class MakeTree {
    public static void main(String[] args) {
        TreeComponent tree = new RealTree();
        TreeComponent bottomDoneTree = new AddStar(tree);
        TreeDecoratorClient client = new TreeDecoratorClient(bottomDoneTree);
        client.decorate();
    }
}

 

다시 한 번 구조를 생각해보자.

 

구조

  • Component : 동적으로 추가할 서비스를 가질 가능성이 있는 객체에 대한 인터페이스
  • ConcreteComponent : 추가적인 서비스가 실제로 정의되어야 할 필요가 있는 객체
  • Decorator : Component객체에 대한 참조자를 관리하면서 Component에 정의된 인터페이스를 만족하도록 인터페이스를 정의 
  • ConcreteDecorator : Component에 새롭게 추가할 서비스를 실제로 구현하는 클래스 

 

Tree.java는 Component에 해당한다. Tree를 만드는 나의 방식은 merryChristmas()메소드를 통한 것이다.

 

이를 이제 ConcreteComponent, Decorator를 이용해서 상속받아 구현한다. 

 

RealTree.java는 Tree.java를 통해 구현한 ConcreteComponent다. 우리가 계획을 세운 것을 실제로 눈앞에 대령했다고 생각하자

 

그러면 이제 꾸밀 차례다. Decorator로는 현재 Star가 있다. 이것도 Tree의 부분으로 결국은 Tree의 부분이 될 것이다. 

 

또 자세히 보면 ConcreteDecorator에 속한다는 것을 확인할 수 있다.

 

즉, Decorator도 Coponent를 상속받아서 구현했다는 것이다. 그래서 앞에서 이것을 Component의 Wrapper라고 표현한 것이다. 

 

그러면 main에서 간단하게 실행을 해보겠다.

MakeTree.java

public class MakeTree {
    public static void main(String[] args) {
        Tree tree = new RealTree();
        Tree bottomDoneTree = new Star(tree);
        TreeDecoratorClient client = new TreeDecoratorClient(bottomDoneTree);
        client.decorate();
    }
}

 

 

여기서 내가 다른 장식을 추가하고 싶다면 어떻게 될까?

Socks.java

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Socks implements Tree {

    private Tree decoComponent;

    public Socks(Tree decoComponent) {
        this.decoComponent = decoComponent;
    }

    @Override
    public String marryChristmas() {
        String result = decoComponent.marryChristmas();
        String addedTree = "socks -- " + result + " -- socks";
        log.info("socks 꾸미기 전... {}", result);
        log.info("socks 꾸민 후... {}", addedTree);
        return addedTree;
    }
}

MakeTree.java

public class MakeTree {
    public static void main(String[] args) {
        Tree tree = new RealTree();
        Tree starAddedTree = new Star(tree);
        Tree socksAddedTree = new Socks(starAddedTree);
        TreeDecoratorClient client = new TreeDecoratorClient(socksAddedTree);
        client.decorate();
    }
}

 

 

이렇게 한다면 기존의 component는 변경하지 않고 구조적으로 계~속 새로운 기능을 추가할 수 있다.

3. 또 다른 예

The Decorator pattern attaches the additional responsibility to an object dynamically.
Decorators provide flexible alternative to some classes for extending funtionality.

결국 decorator pattern의 목적은

 

어떤 작업을 수정하고 싶은데 원래의 것을 수정하지 않고 구조적인 방법을 통한 수정을 돕는다.

 

위에서 본 것처럼 Tree는 그대로지만, 구조적인 방법을 통해(재료를 추가) 변화를 모색하는 것이다.

 

 

위처럼 말을 한다는 메소드가 있다고 하자. 그리고 Original을 거쳐서 반환되는 결과는 hello world이다.

 

Original을 수정하지 않고 원래 기능에 관여하는 방법은 Original을 Wrap 하는 것이다. 

 

이런 식으로 기존의 것은 변경하지 않고 작업하는 것이다. 

 

또한, decorator들이 원래의 것처럼 작동하게끔 하여 기능을 확장시키는 것이다.

 

기능 확장을 위해서 상속이 아닌, 구조를 활용하는 것이다.

 

출처 : https://www.youtube.com/watch?v=GCraGHx6gso 

4. 장단점

장점

  • 새로운 클래스를 만들지 않고 기준 기능을 조합할 수 있다.
  • 컴파일 타임이 아닌 런타임에 동적으로 기능을 변경할 수 있다.

단점

  • 데코레이터를 조합하는 코드가 복잡할 수 있다.

앞의 글을 읽었다면 장점에 대해 어느정도 몸소 체험했을 수도 있다. 

 

동적으로 기능이 변경이 가능하다는 것은 flag를 생성해서 알맞는 decorator를 적용하는 된다는 말이다. 


이렇게 오늘은 decorator패턴에 대해 알아보았다. 

 

잘못된 것이 있으면 말해주면 감사하겠습니다. 꾸벅스

 

 

 

'디자인 패턴(구) > 구조 패턴' 카테고리의 다른 글

프록시(Proxy) 패턴이란?  (0) 2022.04.16
퍼사드(FACADE) 패턴이란?  (0) 2022.04.14
복합체(Composite) 패턴이란?  (0) 2022.04.12
브릿지(Bridge) 패턴이란?  (0) 2022.04.07
어탭터(Adaptor) 패턴이란?  (0) 2022.04.07