본문 바로가기

개발

객체에 기능을 동적으로 추가하는 방법: 데코레이터 패턴

반응형


프록시 패턴과 구조적으로 유사한 디자인 패턴들이 존재하며, 이들은 의도에 따라 서로 구분되기도 합니다. 예를 들어, 프록시 패턴은 접근 제어나 로깅과 같은 제어 책임을 추가하는 데 목적이 있는 반면, 데코레이터 패턴은 기능을 유연하게 확장하는 데 그 목적이 있습니다. 이번 글에서는 이러한 의도 차이를 바탕으로 데코레이터 패턴(Decorator Pattern)에 대해 정리해 보겠습니다.

 

 


 

 

데코레이터 패턴 ( Decorator Pattern )

객체 지향 프로그래밍 에서 데코레이터 패턴은 같은 클래스의 다른 인스턴스의 동작에 영향을 주지 않고 개별 객체에 동적으로 동작을 추가할 수 있게 해주는 디자인 패턴입니다.

이 패턴은 기능을 고유한 관심 영역을 가진 클래스 간에 나눌 수 있으므로 단일 책임 원칙을 준수하는 데 종종 유용합니다. 또한 클래스의 기능을 수정하지 않고도 확장할 수 있으므로 개방-폐쇄 원칙을 준수하는데 유용합니다.

데코레이터를 사용하면 완전히 새로운 객체를 정의하지 않고도 객체의 동작을 확장할 수 있으므로 서브 클래싱보다 효율적일 수 있습니다.
(Wikipedia:데코레이터 패턴)

 

데코레이터 패턴의 구조

데코레이터 패턴은 기존 객체(ConcreteComponent)에 기능을 동적으로 추가하거나 확장하고 싶을 때 사용하는 구조로 상속이 아닌 구성을 통해 유연하게 기능을 확장할 수 있따는 점이 큰 특징입니다.

 

✅ 주요 구성 요소

  1. Compoent (컴포넌트 인터페이스)
    • 공통 인터페이스로 모든 ConcreteComponent와 Decorator는 이 인터페이스를 구현합니다.
  2. ConcreteCompoent (구체 컴포넌트)
    • 실제 기본 기능을 수행하는 클래스로 여기에 추가적인 기능을 덧붙이고 싶을 때 데코레이터를 활용 합니다.
  3. Decorator (추상 데코레이터)
    • Compoent를 구현하고, 내부에 Compoent를 포함하고 있습니다.
    • 기본 구조는 그대로 유지한 채, 자식 클래스에서 기능을 확장하거나 가공할 수 있도록 합니다.
  4. ConcreteDecorator (구체 데코레이터)
    • Decorator를 상속받아 기존 기능에 부가 기능을 추가합니다

📜 흐름 요약

  1. 클라이언트는 Compoent 인터페이스를 통해 동작을 요청
  2. 실제 구현은 ConcreteCompoent가 담당
  3. 필요시 ConcreteDecorator가 이를 감싸서 기능을 추가
  4. 여러 데코레이터를 중첩하여 다양한 조합으로 기능을 구성

 

데코레이터 패턴 예제 코드 : 텍스트 출력 꾸미기

이번 예제는 입력 받은 텍스트를 출력할때 꾸며주는 기능을 추가해보도록 하겠습니다.

// component
public interface Message {
    String getContent();
}

Message (컴포넌트)

  • 모든 메세지 객체가 구현해야 하는 공통 메서드 입니다
// Concrete component
public class SimpleMessage implements Message {
    private String content;

    public SimpleMessage(String content) {
        this.content = content;
    }

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

// Decorator
public abstract class MessageDecorator implements Message {
    protected final Message message;

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

 SimpleMessage (Concrete component)

  • 데코레이터 없이 단독으로 사용할 수 있는 기본 메세지 구현체 입니다

MessageDecorator (Decorator)

  • 다른 Message 객체를 감싸서 기능을 확장하는 기반 클래스 입니다
// Concrete decorator (반복 데코레이터)
public class RepeatDecorator extends MessageDecorator {
    public RepeatDecorator(Message message) {
        super(message);
    }

    @Override
    public String getContent() {
        return message.getContent() + " " + message.getContent();
    }
}

// Concrete decorator (별표 데코레이터)
public class StarDecorator extends MessageDecorator {
    public StarDecorator(Message message) {
        super(message);
    }

    @Override
    public String getContent() {
        return "*" + message.getContent() + "*";
    }
}

RepeatDecorator

  • 메세지 내용을 한 번 더 반복해서 출력하는 데코레이터

StarDecorator

  • 메세지 내용을 "*"로 감싸는 데코레이터
// 기본 메세지 생성
Message message = new SimpleMessage("Hello FIVE");

// 1. 반복 데코레이터
Message repeatDecorator = new RepeatDecorator(message);
System.out.println(repeatDecorator.getContent());
// 출력 : Hello FIVE Hello FIVE

// 2. 별 데코레이터
Message startDecorator = new StarDecorator(message);
System.out.println(startDecorator.getContent());
// 출력 : *Hello FIVE*

// 3. 중첩 데코레이터 - 1
Message repeatAndStartDecorator = new RepeatDecorator(new StarDecorator(message));
System.out.println(repeatAndStartDecorator.getContent());
// 출력 : *Hello FIVE* *Hello FIVE*

// 4. 중첩 데코레이터 - 2
Message startAndRepeatDecorator = new StarDecorator(new RepeatDecorator(message));
System.out.println(startAndRepeatDecorator.getContent());
// 출력 : *Hello FIVE Hello FIVE*

 

예제 1번

  • RepeatDecorator는 내부 message.getContent() 값을 두 번 반복해서 반환합니다.

예제 2번

  • StarDecorator는 메세지 앞뒤에 별표 *를 붙여 반환합니다.

예제 3번

  • 먼저 StarDecorator가 적용되어 "*Hello FIVE*"가 되고, 그 뒤에 RepeatDecorator가 두 번 반복

예제 4번

  • 먼저 RepeatDecorator가 적용 되어 "Hello FIVE Hello FIVE"가 되고, 그 뒤에 StarDecorator가 앞뒤에 *을 붙임 

 

데코레이터 패턴의 특징, 장단점

  • 기존 객체에 기능을 동적으로 추가할 수 있는 패턴
    상속 없이 런타임에 객체의 동작을 유연하게 확장할 수 있음
  • 구성(Composition)을 활용한 유연한 설계
    데코레이터는 원래 객체를 감싸는 방식으로, 객체를 조립하듯 기능을 더할 수 있음
  • OCP(Open-Closed Principle) 준수
    기존 코드를 수정하지 않고 기능을 확장할 수 있어 유지보수가 용이함

 

🌟 장점

  • 기능 추가의 유연성
    상속 없이 런타임에 객체의 동작을 유연하게 확장할 수 있음
  • 단일 책임 원칙(SRP) 적용 가능
    데코레이터는 원래 객체를 감싸는 방식으로, 객체를 조립하듯 기능을 더할 수 있음

 

⚠️ 단점

  • 중첩 구조로 인해 코드 추적이 어려움
    데코레이터가 여러 단계로 중첩되면 실제 실행되는 로직이 복잡하게 느껴질 수 있음
  • 디버깅과 테스트 복잡도 증가
    여러 데코레이터가 조합될 경우, 어느 데코레이터가 어떤 역할을 하는지 파악하기 어려워질 수 있음
  • 객체 생성이 많아짐
    데코레이터를 여러 번 감싸야 하므로 인스턴스 수가 증가할 수 있음

 

📌 정리

데코레이터 패턴은 유연한 기능 확장이 필요한 경우에 유용한 패턴입니다. 특히, 기능 조합의 다양성OCP 준수가 필요한 상황에 적합합니다. 하지만 구조가 복잡해질 가능성이 있으므로, 간단한 기능 확장이나 데코레이터의 중첩이 많지 않을 때 효과적으로 사용할 수 있습니다.

반응형