데코레이터 패턴 (Decorator Pattern)

2025. 4. 13. 23:20·개발지식/디자인패턴
반응형

데코레이터 패턴이란?


데코레이터 패턴(Decorator Pattern)은 객체지향 설계에서 기존 객체의 기능을 수정하지 않으면서 새로운 기능을 동적으로 추가할 수 있게 해주는 구조적 디자인 패턴입니다. 즉, 클래스의 코드를 변경하거나 상속을 통해 서브클래스를 만들지 않고도, 특정 객체에만 새로운 행동을 붙일 수 있습니다 (다른 동일한 클래스의 객체들에는 영향을 주지 않음). 이는 단일 책임 원칙과 개방-폐쇄 원칙을 잘 지원하는데, 각 기능을 별도 클래스로 분리하면서도 기존 코드를 수정하지 않고 확장이 가능하기 때문입니다.

 

데코레이터 패턴은 상속을 사용한 확장에 대한 유연한 대안입니다. 상속은 컴파일 시점에 정적으로 기능을 추가하며 해당 변경이 그 클래스를 사용하는 모든 인스턴스에 영향을 줍니다. 반면 데코레이터를 사용하면 런타임에 개별 객체 단위로 책임을 추가하거나 제거할 수 있어 훨씬 유연합니다. 특히 추가 기능의 종류가 여러 가지이고 조합할 필요가 있을 때 유용한데, 상속만으로 이를 처리하려면 모든 조합을 아우르는 복잡한 서브클래스들을 만들어야 하는 문제가 있습니다. 데코레이터를 활용하면 필요한 데코레이터 객체들을 조합함으로써 원하는 기능 조합을 실시간으로 구성할 수 있습니다​.

 

예를 들어 GUI 윈도우에 스크롤바, 테두리 등의 꾸미기를 조합한다고 할 때, 상속으로는 ScrollingWindow, BorderedWindow, ScrollingBorderedWindow 등 여러 클래스를 만들어야 하지만 데코레이터를 사용하면 스크롤바 데코레이터, 테두리 데코레이터 클래스를 각각 만들고 원하는 만큼 겹쳐서 장착하면 됩니다​.

 

이 설명은 하단의 위키 설명을 인용하였습니다.

 

Decorator pattern - Wikipedia

From Wikipedia, the free encyclopedia Design pattern in object-oriented programming In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behav

en.wikipedia.org

 

데코레이터와 구조가 비슷한 패턴으로 프록시(Proxy) 패턴이 자주 언급됩니다. 두 패턴 모두 어떤 객체를 동일한 인터페이스로 감싸서 사용한다는 공통점이 있지만, 의도(intent)가 다릅니다. 프록시는 실제 객체에 대한 접근을 제어하거나 객체 생성의 지연 초기화 등을 목적으로 대리자 역할을 하는 반면, 데코레이터는 기능을 추가하여 책임을 확장하는 데 목적이 있습니다. 한마디로 “데코레이터는 객체에 새로운 책임을 더하고, 프록시는 객체에 대한 접근을 제어한다”고 구분할 수 있습니다. 

 

또한 어댑터(Adapter) 패턴과도 구별되는데, 어댑터는 인터페이스를 변환하여 호환성 확보에 중점을 두며 기존 기능 자체를 변경하지는 않는다는 점에서 다릅니다. 반면 데코레이터는 인터페이스는 그대로 유지하면서도 부가 기능을 덧붙입니다. 구현 방법 측면에서 보면, 데코레이터는 객체 합성(composition)을 활용하여 기존 객체를 감싸고 호출을 전달(delegation)하는 방식으로 동작하므로, 필요에 따라 여러 겹의 데코레이터를 쌓아 올려도 클라이언트 코드에는 동일한 인터페이스로 동작합니다.

 

 

데코레이터 패턴의 구조와 구성 요소


데코레이터 패턴은 컴포넌트(Component)와 데코레이터(Decorator)라는 두 축을 중심으로 클래스를 구성합니다. 각 구성 요소의 역할은 다음과 같습니다.

데코레이터 패턴 UML 다이어그램
데코레이터 패턴 UML 다이어그램

Component (컴포넌트)

Component는 데코레이터 패턴의 핵심 인터페이스로, 원본 객체와 데코레이터 모두가 구현하는 공통 인터페이스입니다. 이 인터페이스는 클라이언트가 ConcreteComponent와 Decorator를 동일한 방식으로 사용할 수 있게 해 줍니다. 모든 구체적인 컴포넌트와 데코레이터는 이 인터페이스의 메서드를 구현해야 합니다.

 

ConcreteComponent (구상 컴포넌트)

ConcreteComponent는 Component 인터페이스를 구현하는 실제 객체로, 기본 기능을 제공합니다. 이는 데코레이터로 추가 기능을 덧붙일 대상이 되는 핵심 객체이며 데코레이터가 없는 원본 객체라고 볼 수 있습니다. 예를 들어 파일에서 데이터를 읽는 FileInputStream 클래스가 이에 해당합니다. 이 클래스는 Component의 인터페이스를 따라 구체적인 핵심 동작(operation() 등)을 구현합니다.

 

Decorator (추상 데코레이터)

Decorator는 Component 인터페이스를 구현하는 추상 클래스로, 자기 내부에 Component 타입에 대한 레퍼런스를 보유합니다. 데코레이터의 기본 역할은 자신이 감싸고 있는 컴포넌트로 모든 요청을 다시 전달(delegate)하는 것이며, 이를 통해 데코레이터는 Component 인터페이스를 구현하면서 내부적으로 실제 작업을 위임하게 됩니다. 추상 데코레이터 자체에서는 기본 위임 동작만 정의하며, 구체적인 추가 기능은 없거나 비어 있습니다.

 

ConcreteDecorator (구상 데코레이터)

ConcreteDecorator는 Decorator 클래스를 상속받아 실제 추가 기능을 구현하는 클래스입니다. 이 클래스들은 Component의 기본 동작을 확장하거나 수정하는 책임을 가집니다. 하나의 ConcreteDecorator는 한 가지 책임(추가기능)을 추가 구현하며, Component의 메서드를 오버라이드해서 '원본 동작 전후로 필요한 행위를 수행'하거나, 경우에 따라서는 인터페이스에 없는 새로운 메서드를 제공하기도 합니다. 그런 다음 나머지 호출은 내부의 Component 참조에게 위임하여 원본 기능을 수행합니다. 예를 들어 BufferedInputStream은 읽기 전에 버퍼링 로직을 추가하고, DataInputStream은 기본 바이트 읽기 동작 후에 데이터를 원하는 형식으로 변환해 돌려주는 식입니다. 데코레이터들은 이렇게 여러 개가 체인처럼 연결될 수 있으며, 각각의 데코레이터가 순서대로 추가 행동을 수행한 뒤 최종적으로 실제 컴포넌트의 동작을 호출합니다.

 

요약: Component는 공통 인터페이스, ConcreteComponent는 기본 기능 구현, Decorator는 Component를 감싸는 추상 클래스, ConcreteDecorator는 실질적으로 추가 기능을 구현한 클래스입니다. 데코레이터 객체는 Component를 포함하고 동일한 인터페이스를 제공하므로 투명하게 사용될 수 있고, 필요하면 데코레이터 여러 개를 누적하여 기능을 계속 추가할 수 있습니다.

 

 

데코레이터 패턴의 주요 특징


1. 인터페이스 일관성과 투명한 사용

데코레이터와 핵심 컴포넌트는 동일한 인터페이스를 구현합니다. 이로 인해 클라이언트 코드는 순수한 객체를 사용하든 데코레이터로 감싼 객체를 사용하든 차이를 느끼지 못합니다. 코드는 어떤 구현체를 사용하는지 알 필요 없이 인터페이스에만 의존하므로, 데코레이터 추가/제거가 기존 코드에 영향을 주지 않습니다.

 

2. 기능의 계층적 확장과 조합

데코레이터 패턴의 가장 강력한 특징은 여러 데코레이터를 중첩해서 사용할 수 있다는 점입니다. 마치 레고 블록처럼 다양한 기능을 쌓아가며 원하는 조합을 만들 수 있습니다. 예를 들어 '암호화 + 압축 + 로깅 기능'을 원한다면 세 개의 데코레이터를 순차적으로 적용하기만 하면 됩니다. 이런 유연성은 상속으로는 달성하기 어려운 수준의 기능 조합을 가능하게 합니다.

 

3. 단일 책임 원칙의 실현

각 데코레이터는 명확하게 정의된 하나의 책임만 가집니다. 암호화 데코레이터는 암호화만, 압축 데코레이터는 압축만 담당하므로 코드가 더 명확해지고 테스트하기 쉬워집니다. 이런 관심사의 분리는 복잡한 기능을 작고 관리하기 쉬운 단위로 나누어 전체 시스템의 유지보수성을 높입니다.

 

4. 런타임 동적 확장성

데코레이터 패턴은 컴파일 타임이 아닌 런타임에 동적으로 객체의 기능을 확장할 수 있게 해줍니다. 이는 상속과의 핵심적인 차이점입니다. 여기서 '동적'이란 프로그램이 실행되는 도중에 '조건'에 따라 객체의 기능을 추가할 수 있다는 의미입니다. 상속을 사용하면 객체의 모든 기능이 컴파일 시점에 결정되어 변경할 수 없지만, 데코레이터 패턴은 사용자 입력이나 시스템 상태 같은 런타임 조건에 따라 객체에 새로운 기능을 덧붙일 수 있습니다. 예를 들어, 파일 저장 시스템에서 사용자가 '암호화' 옵션을 선택했을 때만 암호화 기능을 추가하거나, 특정 상황에서만 로깅 기능을 활성화하는 등의 유연한 확장이 가능합니다. 이런 특성은 실행 시점의 요구사항에 맞게 객체의 행동을 조정해야 하는 복잡한 시스템에서 특히 가치가 있습니다.

 

5. 개방-폐쇄 원칙 준수

데코레이터 패턴은 "확장에는 열려있고, 수정에는 닫혀있다"는 개방-폐쇄 원칙(OCP)을 자연스럽게 따릅니다. 새로운 기능이 필요할 때 기존 코드를 변경하는 대신, 새로운 데코레이터를 만들어 기능을 확장합니다. 이렇게 하면 기존 코드의 안정성을 해치지 않으면서도 시스템에 새로운 기능을 추가할 수 있습니다.

 

6. 상속의 대안으로서의 합성

데코레이터 패턴은 "상속보다 합성을 사용하라"는 객체지향 설계 원칙을 실천합니다. 상속은 강력하지만 부모-자식 관계가 컴파일 타임에 고정되고, 부모 클래스의 모든 기능을 물려받는 한계가 있습니다. 반면 데코레이터 패턴은 객체 합성을 통해 필요한 기능만 선택적으로 추가할 수 있어 더 유연한 설계가 가능합니다.

 

 

자바 예시: 입출력 스트림 데코레이터


Wiki에 의하면 Java의 입출력(I/O) 라이브러리는 데코레이터 패턴의 대표적인 활용 사례로 자주 언급된다고 합니다. 예를 들어 파일에서 데이터를 읽는 스트림을 생각해 봅시다. Java에서는 InputStream이라는 추상 클래스(컴포넌트)가 있고, 구체 컴포넌트로 FileInputStream이 파일을 한 바이트씩 읽는 기본 기능을 제공합니다. 이때 버퍼링 기능을 추가하고 싶다면 BufferedInputStream이라는 데코레이터를 사용할 수 있습니다.

 

BufferedInputStream은 FilterInputStream (추상 데코레이터의 한 종류)을 상속받은 구상 데코레이터로서, 내부에 또 하나의 InputStream을 가지고 read 작업을 위임하면서 한 번에 많은 양을 읽어 임시 버퍼에 저장해 둡니다 (여러 바이트를 미리 읽어둔 후 요청 시 한 바이트씩 반환하여 속도를 높임).

 

또한 데이터 해석 기능을 추가하고 싶다면 DataInputStream 데코레이터를 감싸서 readInt()나 readDouble()처럼 기본 바이트 스트림에는 없는 메서드로 원시 타입 데이터를 읽어올 수도 있습니다. 이러한 InputStream 계열 클래스들은 모두 InputStream이라는 같은 추상 타입을 공유하므로, 아래와 같이 연쇄적으로 데코레이터를 연결하여 사용할 수 있습니다.

자바 input stream 예시
자바 input stream 예시

다음으로 Reader 예시코드를 살펴봅시다.

public class DecoratorIOExample {

    public static void main(String[] args) throws IOException {
        // 1. 텍스트 파일을 읽는 기본 파일 리더 (ConcreteComponent)
        FileReader fileReader = new FileReader("example.txt");
        
        // 2. 버퍼링 기능을 추가한 데코레이터 (ConcreteDecorator)
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        
        // BufferedReader를 통해 텍스트를 한 줄씩 읽을 수 있음 (추가된 기능)
        String line;
        
        // readLine은 BufferedReader가 제공
        while ((line = bufferedReader.readLine()) != null) {
            System.out.println(line);
        }
        
        // 데코레이터를 닫으면 내부의 fileReader도 함께 닫힘
        bufferedReader.close();
    }
    
}
  • 위의 코드에서 버퍼링 기능을 추가한 데코레이터 객체는 다음과 같이 생성됩니다.
// BufferedReader 클래스의 생성자 -> 여기서 FileReader를 담아줍니다.
public BufferedReader(Reader in) {
    this(in, DEFAULT_CHAR_BUFFER_SIZE);
}

// 위의 생성자에서 this 메서드가 호출되면 아래 생성자가 호출됩니다.
public BufferedReader(Reader in, int sz) {
    super(in);
    if (sz <= 0)
        throw new IllegalArgumentException("Buffer size <= 0");
    this.in = in;
    cb = new char[sz];
    nextChar = nChars = 0;
}

// 최종적으로 최상위 abstract class인 Reader의 생성자가 호출됩니다.
Reader(Reader in) {
    Class<?> clazz = in.getClass();
    if (getClass() == BufferedReader.class &&
            (clazz == InputStreamReader.class || clazz == FileReader.class)) {
        this.lock = InternalLock.newLockOr(in);
    } else {
        this.lock = in;
    }
}

코드 내용을 정리해 봅시다.

위 코드에서 FileReader만 사용할 경우 파일로부터 한 문자(char)씩 읽어야 하지만, BufferedReader 데코레이터를 추가하면 readLine()이라는 메서드로 한 줄씩 편리하게 읽어올 수 있습니다. BufferedReader는 내부에서 FileReader의 read()를 반복 호출하며 버퍼를 채우고 줄 바꿈 문자를 탐지하여 한 줄 문자열을 만들어주는 기능을 제공하는 것입니다.

 

이처럼 데코레이터를 통해 기본 스트림에 없던 고급 기능(라인 단위 처리)을 컴포지션을 통해 확장하였으며, 클라이언트 (main 메서드) 입장에서는 BufferedReader도 Reader의 한 구현체이므로 read() 등의 메서드를 동일하게 사용할 수 있다는 점에서 인터페이스 호환성이 유지됩니다. Java의 java.io 패키지에는 이 외에도 FilterOutputStream을 상속한 BufferedOutputStream, PrintStream/PrintWriter (포맷된 출력 기능 추가) 등 다수의 데코레이터 클래스가 존재하며, Java I/O 스트림 구현은 전반적으로 데코레이터 패턴에 의해 설계되어 있습니다.

 

 

데코레이터 패턴을 언제 활용하면 좋을까?


위에서 설명한 것들을 정리하면서, 어떤 상황에서 데코레이터 패턴을 적용하는 것이 유용한지 몇 가지 실무 예시를 들어보겠습니다. 데코레이터 패턴은 기존 코드를 수정하지 않고 새로운 기능을 첨가하거나, 객체의 책임을 동적으로 변경해야 할 때 유용하므로 다음과 같은 경우 고려할 수 있습니다.

 

1. 기능 확장이 필요한 경우

설계 당시엔 없었던 '새로운 기능을 기존 클래스에 추가'해야 하는 상황이 생길 수 있습니다. 만약 해당 기능을 클래스의 서브클래스를 만들어 넣을 수도 있지만, 그렇게 하면 코드 중복이나 클래스 폭증 문제가 발생할 수 있습니다. 이때 데코레이터 패턴을 사용하면 기존 클래스는 그대로 두고도 새로운 기능을 덧붙일 수 있습니다. 예를 들어 어떤 파일 저장 모듈에 나중에 '데이터 암호화/압축 기능'을 추가해야 한다면, 파일 저장 클래스(컴포넌트)를 감싸는 '암호화 데코레이터'나 '압축 데코레이터'를 구현하여, 저장 직전에 데이터 암호화 또는 압축을 수행하고 나서 원본 저장 기능을 호출하도록 만들 수 있습니다. 이렇게 하면 기존 저장 모듈의 코드 변경 없이도 안전한 저장이라는 새로운 책임을 추가할 수 있습니다. 

 

파일 저장 모듈을 데코레이터 패턴으로 확장시켜 봅시다.

// 기본 컴포넌트 인터페이스
public interface FileStorage {
    void save(String data, String filename);
}

// 기본 컴포넌트 구현
public class BasicFileStorage implements FileStorage {
    @Override
    public void save(String data, String filename) {
        System.out.println("기본 저장: " + data + "를 " + filename + "에 저장");
        // 실제 파일 저장 로직
    }
}

// 데코레이터 추상 클래스
public abstract class FileStorageDecorator implements FileStorage {
    protected FileStorage wrapped;
    
    public FileStorageDecorator(FileStorage wrapped) {
        this.wrapped = wrapped;
    }
}

// 암호화 데코레이터
public class EncryptionDecorator extends FileStorageDecorator {
    public EncryptionDecorator(FileStorage wrapped) {
        super(wrapped);
    }
    
    @Override
    public void save(String data, String filename) {
        String encrypted = encrypt(data);
        wrapped.save(encrypted, filename);
    }
    
    private String encrypt(String data) {
        System.out.println("데이터 암호화 수행");
        return "암호화된_" + data;
    }
}

// 압축 데코레이터
public class CompressionDecorator extends FileStorageDecorator {
    public CompressionDecorator(FileStorage wrapped) {
        super(wrapped);
    }
    
    @Override
    public void save(String data, String filename) {
        String compressed = compress(data);
        wrapped.save(compressed, filename);
    }
    
    private String compress(String data) {
        System.out.println("데이터 압축 수행");
        return "압축된_" + data;
    }
}

다음과 같이 사용됩니다.

// 사용 예시
public class Main {

    public static void main(String[] args) {
        // 기본 저장
        FileStorage simple = new BasicFileStorage();
        simple.save("Hello World", "test.txt");
        
        // 암호화 후 저장
        FileStorage encrypted = new EncryptionDecorator(new BasicFileStorage());
        encrypted.save("Hello World", "encrypted.txt");
        
        // 압축 후 저장
        FileStorage compressed = new CompressionDecorator(new BasicFileStorage());
        compressed.save("Hello World", "compressed.txt");
        
        // 압축 후 암호화 후 저장 (데코레이터 조합)
        FileStorage compressedAndEncrypted = 
            new EncryptionDecorator(new CompressionDecorator(new BasicFileStorage()));
        compressedAndEncrypted.save("Hello World", "secure_archive.txt");
    }
    
}

데이터 흐름을 살펴봅시다.

아래와 같이 가장 바깥쪽 데코레이터(EncryptionDecorator)부터 자신의 고유 기능을 수행한 후 wrapped.save() 메서드를 호출하면서 내부 객체에게 제어권을 넘깁니다. 이런 식으로 기능이 안쪽으로 연쇄적으로 호출되면서 각 단계마다 데이터가 변형됩니다.

1. 맨 처음 compressedAndEncrypted.save("Hello World", "file.txt") 호출
2. → EncryptionDecorator의 save() 실행 (데이터 암호화)
3. → → CompressionDecorator의 save() 실행 (데이터 압축)
4. → → → BasicFileStorage의 save() 실행 (실제 파일 저장)

다이어그램으로 표현하면 다음과 같습니다.

파일 저장 데코레이터 패턴 적용
파일 저장 데코레이터 패턴 적용

2. 로그 (Logging)

애플리케이션 개발에서 로깅은 대표적인 부가 기능입니다. 핵심 비즈니스 로직과는 별개로 함수 진입/종료 시점이나 입력값/결과 등을 기록해야 할 때, 해당 로직을 직접 코드에 넣으면 중복이 많아지고 관리가 어렵습니다. 대신 서비스나 DAO 등의 인터페이스를 대상으로 로깅 데코레이터를 만들어 적용하면 효과적입니다. 로깅 데코레이터는 같은 인터페이스를 구현하면서, 메서드 호출 시 내부에서 실제 객체의 메서드를 부르기 전에 로그를 남기고 (필요하다면 호출 후에도 로그), 그 결과를 반환합니다. 예를 들어 아래처럼 구현할 수 있습니다.

class LoggingServiceDecorator implements SomeService {

    private SomeService delegate;
    
    public LoggingServiceDecorator(SomeService delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public Result doWork(String input) {
        System.out.println("[LOG] doWork 호출 - input: " + input);
        Result result = delegate.doWork(input);   // 실제 서비스 호출
        System.out.println("[LOG] doWork 종료 - result: " + result);
        return result;
    }
    
}

이렇게 하면 SomeService를 사용하는 클라이언트 코드는 LoggingServiceDecorator로 감싼 인스턴스를 주입받아 동일하게 doWork() 메서드를 호출하지만, 실제로는 '자동으로 앞뒤에 로그가 기록되는' 효과를 얻게 됩니다. 또한 조건부 로깅도 데코레이터로 구현하기 좋은데, 예를 들어 디버그 모드에서만 로그를 남기고 싶다면 로깅 데코레이터 내부에 조건문을 넣거나, 애플리케이션 설정에 따라 데코레이터를 적용할지 말지 결정하면 됩니다. (로그 레벨에 따라 다른 데코레이터를 붙이는 것도 가능할 것입니다.)

3. 캐싱 (Caching)

반복되는 연산의 결과를 저장해 두었다가 성능을 향상시키고 싶을 때도 데코레이터를 적용할 수 있습니다. 예를 들어, 원본으로 데이터베이스나 API 호출을 통해 데이터를 가져오는 DataService가 있고 이 결과를 '캐싱'하고 싶다면, 해당 인터페이스를 구현한 '캐싱 데코레이터'를 만들 수 있습니다. 아래 코드는 간단한 캐싱 데코레이터의 예시입니다.

interface DataService {
    String fetchData(String key);
}

class RealDataService implements DataService {
    public String fetchData(String key) {
        // 실제 데이터 조회 (예: DB나 원격 API 호출)
        System.out.println("Fetching from source for " + key);
        // ... (시간이 오래 걸리는 연산 가정)
        return "DATA_FOR_" + key;
    }
}

// 데코레이터: 결과 캐싱 기능 추가
class CachedDataService implements DataService {

    private DataService delegate;
    private Map<String, String> cache = new HashMap<>();
    
    public CachedDataService(DataService delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public String fetchData(String key) {
        if (cache.containsKey(key)) {
            System.out.println("[Cache] Hit for " + key);
            return cache.get(key);  // 캐시된 값 반환
        }
        
        System.out.println("[Cache] Miss for " + key + ", fetching...");
        String result = delegate.fetchData(key);   // 실제 조회 수행
        cache.put(key, result);                    // 결과를 캐시에 저장
        return result;
    }
    
}

CachedDataService 데코레이터는 DataService 인터페이스를 구현하므로 투명하게 교체될 수 있습니다. 캐시 맵을 내부에 가지고 있어서, 요청된 key에 대한 데이터가 이미 캐시에 있으면 실제 서비스를 부르지 않고 저장된 값을 바로 반환합니다. 캐시에 없을 경우에만 실제 delegate의 fetchData를 호출하고 결과를 캐시에 담아둡니다. 이렇게 하면 클라이언트는 평소와 똑같이 fetchData()를 호출하지만, 중복 호출 시에는 내부적으로 캐시 된 값이 반환되므로 성능이 향상됩니다. 이 패턴은 결과를 저장해 두고 재사용하는 메모이제이션(memoization)과도 관련이 있으며, 구현 상 특별한 제약이 없어서 필요에 따라 여러 서비스에 적용할 수 있습니다 (예: API 호출 캐싱, 계산 결과 캐싱 등).

4. 권한 체크 및 인증 처리

보안 영역에서도 데코레이터 패턴이 응용될 수 있습니다. 메서드 호출 전에 '사용자 인증이나 권한 확인'이 필요한 경우, 서비스 객체를 감싸는 '보안 데코레이터'를 만들어 적용할 수 있습니다. 예를 들어 OrderService 인터페이스의 구현체를 사용하기 전에, 다음과 같은 데코레이터를 적용하면 관리자가 아닌 사용자가 호출할 때 예외를 던지는 식으로 권한 검사를 추가할 수 있습니다.

class AuthenticatedOrderService implements OrderService {

    private OrderService delegate;
    
    public AuthenticatedOrderService(OrderService delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public Order getOrderDetails(String orderId) {
        if (!UserContext.currentUser().hasRole("ADMIN")) {
            throw new SecurityException("권한 없음");
        }
        return delegate.getOrderDetails(orderId);
    }
    // ... OrderService의 다른 메서드들도 유사하게 위임 전에 체크 ...
    
}

이처럼 보안 데코레이터는 메서드 진입 시점에 사용자 컨텍스트를 확인하여 권한이 충분하지 않으면 아예 내부 서비스 호출을 하지 않고 예외를 발생시킵니다. 결과적으로 핵심 비즈니스 로직 (OrderService의 실제 구현)은 수정되지 않으면서도 '인증/권한 검증'이라는 부가 책임이 투명하게 추가됩니다. Spring AOP의 @Secured, @PreAuthorize 등이 내부적으로 이런 프록시 데코레이터를 생성하여 동작하는 원리입니다.

 

 

출처


https://en.wikipedia.org/wiki/Decorator_pattern

 

Decorator pattern - Wikipedia

From Wikipedia, the free encyclopedia Design pattern in object-oriented programming In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behav

en.wikipedia.org

https://medium.com/@bubu.tripathy/design-patterns-used-in-spring-framework-60df94fd3400#:~:text=HandlerInterceptor%20interface%20to%20provide%20additional,Here%20is%20an%20example

 

Design Patterns used in Spring Framework

Spring Framework is a popular Java framework for building enterprise-level applications. It provides a wide range of features and…

medium.com

https://docs.spring.io/spring-framework/docs/1.2.x/reference/aop.html#:~:text=CGLIB%20proxying%20works%20by%20generating,pattern%2C%20weaving%20in%20the%20advice

 

Chapter 6. Spring AOP: Aspect Oriented Programming with Spring

If you're using the Spring IoC container (an ApplicationContext or BeanFactory) for your business objects--and you should be!--you will want to use one of Spring's AOP FactoryBeans. (Remember that a factory bean introduces a layer of indirection, enabling

docs.spring.io

 

반응형

'개발지식 > 디자인패턴' 카테고리의 다른 글

전략 패턴(Strategy Pattern)  (0) 2025.04.15
옵저버 패턴(Observer Pattern)  (0) 2025.04.12
전략 패턴(Strategy Pattern)이란?  (2) 2024.10.06
커맨드 패턴(Command Pattern)이란?  (4) 2024.10.04
가볍게 알아보는 디자인 패턴 - 퍼사드 패턴(Facade Pattern)  (5) 2023.12.11
'개발지식/디자인패턴' 카테고리의 다른 글
  • 전략 패턴(Strategy Pattern)
  • 옵저버 패턴(Observer Pattern)
  • 전략 패턴(Strategy Pattern)이란?
  • 커맨드 패턴(Command Pattern)이란?
Stark97
Stark97
문의사항 또는 커피챗 요청은 링크드인 메신저를 보내주세요! : https://www.linkedin.com/in/writedev/
  • Stark97
    오늘도 개발중입니다
    Stark97
  • 전체
    오늘
    어제
    • 분류 전체보기 (246)
      • 개발지식 (20)
        • 스레드(Thread) (8)
        • WEB, DB, GIT (3)
        • 디자인패턴 (8)
      • JAVA (21)
      • Spring (88)
        • Spring 기초 지식 (35)
        • Spring 설정 (6)
        • JPA (7)
        • Spring Security (17)
        • Spring에서 Java 활용하기 (8)
        • 테스트 코드 (15)
      • 아키텍처 (6)
      • MSA (15)
      • DDD (11)
      • gRPC (9)
      • Apache Kafka (18)
      • DevOps (23)
        • nGrinder (4)
        • Docker (1)
        • k8s (1)
        • 테라폼(Terraform) (12)
      • AWS (32)
        • ECS, ECR (14)
        • EC2 (2)
        • CodePipeline, CICD (8)
        • SNS, SQS (5)
        • RDS (2)
      • notion&obsidian (3)
      • 동아리 (0)
  • 링크

    • notion기록
    • 깃허브
    • 링크드인
  • hELLO· Designed By정상우.v4.10.0
Stark97
데코레이터 패턴 (Decorator Pattern)
상단으로

티스토리툴바