도서 요약 / / 2023. 2. 16. 20:21

디자인 패턴 - 데코레이터(decorator) 패턴

udemy 강좌(Java Design Patterns & SOLID Design Principles)를 정리한 내용이다.

https://www.udemy.com/course/design-patterns-in-java-concepts-hands-on-projects/learn/lecture/9604610?start=0#overview


데코레이터는 무엇인가?

데코레이터는 구조 패턴이고 이해하고 구현하기 매우 쉬우며 여러가지 기능을 제공한다.

  • 기존 객체의 동작을 동적으로 변경하고 싶을 때 데코레이터 디자인 패턴을 사용할 수 있다.
  • 데코레이터는 그 내부의 객체를 감싸고 감싸여진 객체와 동일한 인터페이스를 제공한다. 그래서 기존 클라이언트를 변경할 필요가 없다.
  • 데코레이터는 기존 클래스 기능을 확장하기 위해 서브클래싱을 제공한다.

데코레이터 구현

  • 컴포넌트 작성으로 시작
    • 컴포넌트는 클라이언트에 필요한 인터페이스를 정의한다.
    • 구체 컴포넌트는 컴포넌트 인터페이스를 구현한다.
    • 데코레이터를 정의한다. 데코레이터는 컴포넌트를 구현하고 구체 컴포넌트로의 참조가 필요하다.
    • 데코레이터 메소드에서 구체 컴포넌트 인스턴스에서 제공된 기능 외에 추가적인 동작을 제공한다.
  • 데코레이터도 추상 클래스가 될 수 있고 기능을 제공하기 위해 서브클래스에 의존할 수 있다.

예제: UML

우리가 하려고 하는 것은 Message 클래스를 호출할 것이다. 이 클래스는 추상 클래스나 인터페이스가 될 것이고 getContent를 호출할 것이다. Message content를 문자열로 제공할 것이다. 이 컴포넌트에는 TextMessage라는 하나의 구체 구현부가 있다. getContent의 구현부를 가지게 될 것이고 단순한 문자인 message의 상태가 있다.

다음 다이어그램에서 다른 두 개의 데코레이터가 있다. 우선 Base64EncodedMessage가 있는데 이 데코레이터가 하려고 하는 것은 Message를 확장하고 합성 관계에 있다는 것을 알 수 있다. 그래서 동작하는데 TextMessage 객체가 필요하다. 이 데코레이터는 MessageContent를 인코딩한다. Base64EncodedMessage에서 getContent를 호출할 때 base64로 인코딩된 Message content를 받을 것이다.

비슷한 위치에 HTMLEncodedMessage라는 추가적인 데코레이터를 구현할 것이다. 이것이 하는 일은, 예상하겠지만 Message String이 Html로 인코딩되고 클라이언트에 리턴된다.

Java 구현

public interface Message {

    String getContent();
}

Message 인터페이스가 있고 getContent라는 단일 메소드를 제공한다.

@AllArgsConstructor
public class TextMessage implements Message {

    private String msg;

    @Override
    public String getContent() {
        return msg;
    }
}

그리고 TextMessage가 있다. 이것은 Message를 구현하는 구체 컴포넌트이고 간단한 텍스트 메시지를 가지고 있고 단일 문자열을 리턴하는 getContent 메소드의 구현부를 제공한다.

public class Client {

    public static void main(String[] args) {
        Message m = new TextMessage("Type <FORCE> is strong with this one!");
        System.out.println(m.getContent());
    }
}

클라이언트 코드를 보면 Message 객체를 사용하는 곳이고 TextMessage의 새로운 객체를 만들고 있다는 것을 알 수 있고 몇 개의 문자열을 제공하고 getContent를 호출하여 출력한다. 이 코드를 실행한다면, 아직 데코레이터는 적용하지 않았다.

public class HtmlEncodedMessage implements Message {

    private Message message;

    public HtmlEncodedMessage(Message message) {
        this.message = message;
    }

    @Override
    public String getContent() {
        return StringEscapeUtils.escapeHtml4(message.getContent());
    }
}

HTMLEncodedMessage라는 클래스가 있다. 이것이 TextMessage가 저장되는 문자열을 포함하는 데코레이터가 될 것이고 HTML 인코딩을 포함한다.

public class Client {

    public static void main(String[] args) {
        Message m = new TextMessage("Type <FORCE> is strong with this one!");
        System.out.println(m.getContent());

        Message decorator = new HtmlEncodedMessage(m);
        System.out.println(decorator.getContent());
    }
}

다시 클라이언트 코드로 가서 이 데코레이터를 사용해 볼 것이다.

public class Base64EncodedMessage implements Message {

    private Message message;

    public Base64EncodedMessage(Message message) {
        this.message = message;
    }

    @Override
    public String getContent() {
        return Base64.getEncoder().encodeToString(message.getContent().getBytes());
    }
}

Base64EncodedMessage라는 또 다른 데코레이터가 있다.

public class Client {

    public static void main(String[] args) {
        Message m = new TextMessage("Type <FORCE> is strong with this one!");
        System.out.println(m.getContent());

        Message decorator = new HtmlEncodedMessage(m);
        System.out.println(decorator.getContent());

        decorator = new Base64EncodedMessage(decorator);
        System.out.println(decorator.getContent());
    }
}

클라이언트 코드에서 Base64EncodedMessage를 초기화할 것이다.

구현 고려사항

  • 공통 컴포넌트를 확장하는 데코레이터와 구체 클래스가 있기 때문에 데코레이터에서 그러한 상태가 필요없을 수 있으므로 기반 클래스에서는 많은 상태를 가지면 안된다.
  • 데코레이터 메소드에 equalshashCode를 잘 확인해야 한다. 데코레이터를 사용할 때 데코레이터가 없을 때와 동일 객체인지 결정해야 한다.
  • 데코레이터는 순환 합성(recursive composition)을 지원하고 이 패턴은 단지 작은 기능만을 추가하는 간단한 객체생성을 사용한다. 이 객체를 사용한 코드는 디버깅하기가 어려워진다.

디자인 고려사항

  • 데코레이터는 상속보다 유연하고 강력한 기능이다. 상속은 정적인 상태이지만 데코레이터는 런타임에 객체를 사용하여 동적으로 구성할 수 있다.
  • 데코레이터는 객체를 한꺼풀 더 씌우는 방식으로 동작한다. 객체의 원래 기능에 작은 기능만을 추가해야 한다. 기능의 원래 의미를 변경해서는 안된다.

위험요소

  • 보통은 시스템에 작은 기능을 가진 많은 클래스들이 추가된다. 결국 많은 객체가 생성이 되고 내부에 하나씩 끼워넣게 된다.
  • 때로는 신참자들이 모든 경우에 상속을 대신해서 사용한다. 데코레이터를 기존 객체를 감싸고 있는 얇은 막 정도로 생각해라.



반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유