본문 바로가기

Computer Science/Design Pattern

[Design Pattern] 데코레이터 패턴(Decorator Pattern)에 대해서 - 컴도리돌이

728x90

 

데코레이터 패턴(Decorator Pattern)은 객체의 기능을 확장하거나 수정할 때 사용되는 디자인 패턴이에요. 유연하게 확장을 할 수 있기에, 정적 또는 동적으로 객체에 새로운 기능을 부여할 수 있어요. 일상생활에서는 선물 포장이 떠오르는데, 선물을 준비할 때 종이나 리본, 스티커 등을 사용해서 선물을 포장합니다. 이렇게 포장을 추가하는 것은 기존 선물을 변경하지 않고도 새로운 요소를 덧붙여 선물을 만드는 것과 유사해요.  데코레이터 패턴도 이와 비슷하게 기존 객체를 그대로 유지하면서 새로운 기능을 추가하거나 변경함으로써 객체를 더욱 강화시킵니다. 이렇게 하면 기본 객체는 변함없이 유지되면서도 새로운 기능을 더해주어 객체의 활용도를 높일 수 있어요. 

 


데코레이터  패턴 구조(Decorator Pattern Structure)

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

  • Component(구성요소): component는 동적으로 책임을 추가할 수 있는 객체의 인터페이스를 정의.
  • ConcreteComponent(구체적인 구성 요소): Component 인터페이스의 구현체.
  • Decorator(장식자): Decorator는 Component에 대한 참조를 가지고 있으며, 또한 Component 인터페이스를 준수.
  • ConcreteDecorator(구체적인 장식자): ConcreteDecorator는 원래의 Component에 책임을 추가.

데코레이터 패턴 예시(Decorator Pattern Example) 

1) 클린 하지 못한 코드 

데코레이터 패턴이 필요한 경우를 살펴볼까요? 제가 다운로드 기능을 만들고 있다고 가정해 보겠습니다.

 

public class Downloader {
    public void download(String url) {
        System.out.println("Downloading file from: " + url);
    }
}

 

위와 같이 다운로드 기능을 갖고 있는 클래스를 만들었습니다.  근데 사용자들은 기존 다운로드 기능보다는 해당 파일을 엑셀 형식으로 변환하여 다운로드하기를 원합니다. 그래서 기존 로직에서 추가하는 방식으로 아래와 같이 구현했습니다. 

 

public class Downloader {
    public void download(String url) {
        System.out.println("Downloading file from: " + url);
        convertToExcel();
    }

    private void convertToExcel() {
        System.out.println("Converting downloaded file to Excel format...");
    }
    
}

 

제가 뚝딱뚝딱 잘 만들어서 그런지 이 상태에서 사용자들이 워드 문서로 변환한 파일도 같이 다운로드하고  싶어 합니다. 저는 또 다음과 같이 구현하겠죠.. 😎

 

public class Downloader {
    public void download(String url) {
        System.out.println("Downloading file from: " + url);
        convertToExcel();
        convertToWord();
    }

    private void convertToExcel() {
        System.out.println("Converting downloaded file to Excel format...");
    }
    
    private void convertToWord() {
        System.out.println("Converting downloaded file to Word format...");
    }
    
}

 

위 코드에서는 기능을 직접 다운로드 클래스에 추가하여 구현하였기 때문에, 추가적인 기능을 구현할 때마다 다운로드 클래스를 수정해야 합니다. 이는 코드의 유지 보수를 어렵게 만들고, 클래스의 책임이 너무 많아지면서 코드의 응집도가 낮아집니다. (물론 처음 이렇게 개발을 시작한 저는 해당 클래스를 수정하기 편하지만... 😌😌 )

또한, 기존 코드를 변경으로 인해 다운로드 클래스가 단순 다운로드 기능을 담당하는 것이 아니라 여러 기능을 함께 처리하게 되는 문제가 발생할 수 있습니다. 


다운로드 로직이 있나요?

네. 다운로드 클래스에 있어요.

감사합니다!

이거 왜 다운로드 클래스에서 엑셀/워드 변환까지 같이 시켜줘요?


 

2) 데코레이터 패턴을 적용한 코드

그렇다면 위에 코드를 데코레이터 패턴을 적용해서 다시 확인해 볼까요? 먼저 다운로드 인터페이스를 정의합니다. 

public interface Downloader {
    void download(String url);
}

 

그리고 기본 다운로드 로직을 구현합니다.

 

public class BasicDownloader implements Downloader {
    @Override
    public void download(String url) {
        // 기본 다운로드 로직
        System.out.println("Downloading file from: " + url);
    }
}

 

이제 데코레이터 패턴을 사용하여, 파일을 엑셀로 변환/워드로 변환 기능을 가진 다운로드 클래스를 다음과 같이 구현했습니다. 

 

public class ExcelConversionDecorator implements Downloader {
    private Downloader downloader;

    public ExcelConversionDecorator(Downloader downloader) {
        this.downloader = downloader;
    }

    @Override
    public void download(String url) {
        downloader.download(url);
        System.out.println("Converting downloaded file to Excel format...");
    }
}


public class WordConversionDecorator implements Downloader {
    private Downloader downloader;

    public WordConversionDecorator(Downloader downloader) {
        this.downloader = downloader;
    }

    @Override
    public void download(String url) {
        downloader.download(url);
        System.out.println("Converting downloaded file to Word format...");
    }
}
public class Main {
    public static void main(String[] args) {
        Downloader excelAndWordDownloader = new WordConversionDecorator(new ExcelConversionDecorator(new BasicDownloader()));
        excelAndWordDownloader.download("http://example.com/file.txt");
    }
}

 

위와 같이 데코레이터 패턴을 적용한 예시를 보니깐 어떤가요? 🤔🤔

기존에 있는 다운로드 기능을 변경하지 않으면서 엑셀로 변환하거나, 워드로 변환하거나 등 다양한 기능을 동적으로 추가할 수 있게 되었습니다.  데코레이터 패턴을 적용하면 새로운 요청이 들어올 때마다 매번 새로운 다운로드 기능을 만들 필요 없이, 데코레이터를 추가하거나 변경함으로써 필요한 기능을 동적으로 확장할 수 있어요. 이렇게 함으로써 유지보수가 쉬워지고, 코드의 재사용성이 높아지게 됩니다. 

 


다운로드 로직이 있나요?

네. 다운로드 클래스에 있어요.

감사합니다!

데코레이터 패턴을 적용하셨네요! 너무 멋져요 :>


 

자바 I/O 메서드

 new WordConversionDecorator(new ExcelConversionDecorator(new BasicDownloader()));

 

대게 자바에서 이렇게 생성자들을 감싸는 형태로 객체를 생성하는 경우가 있는데, 대표적으로 자바 I/O가 메서드가 있습니다.

간단하게 자바 I/O 메서드에서는 데코레이터 패턴이 어떻게 적용되었는지 알아볼게요.😪

 

 

Component : InputStream

InputStream은 모든 클래스의 슈퍼클래스이에요.
InputStream의 하위 클래스를 정의해야 하는 응용 프로그램은 항상 입력의 다음 바이트를 반환하는 메서드를 제공해야 합니다.
그러므로 InputStream은 모든 바이트 스트림 입력 클래스의 공통된 기능을 정의하고 있어요.
이 클래스는 상속받은 하위 클래스는 다양한 종류의 입력 소스로부터 바이트를 읽어오는 기능을 구현할 수 있어요.

 

ConcreteComponent: FileInputStream

이 클래스는 기본적인 파일 입력 기능을 제공하고 있으며, 이를 상속받아서 추가적인 기능을 구현하는 데코레이터 클래스들이 확장될 수 있습니다.

 

Decorator: FilterInputStream

FilterInputStream은 InputStream을 상속받고 있으며, 이를 래핑 하여 기능을 추가합니다.
즉, FilterInputSTream은 InputStream에 새로운 기능을 추가하는 역할을 합니다. 

 

ConcreteDecorator: BufferedInputStream

InputStream을 래핑 하여 버퍼링 기능을 추가합니다.
이로써 입력 성능을 향상하는 역할을 수행합니다.

 

import java.io.*;

public class Main {
    public static void main(String[] args) {
        try {
            
           BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("ravindra.txt")));
           while(bis.available() > 0) {
                char c = (char)bis.read();
                System.out.println("Character = "+c);
            }
            
            ...
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

파일 I/O에서 데코레이터 패턴을 적용한 코드입니다. 

코드를 자세히 보면 bis 객체는 BuffteredInputStream FileInputStream을 장식하고 있어요. 

여기서 FileInputStream InputStream의 구현체이고, 

BufferedInputStream InputStream 래핑 하여 버퍼링 기능을 추가됩니다.


데코레이터 패턴 특징 (Decorator Pattern Characteristic)

테코레이터 패턴 사용 시기

  • 객체의 기능을 변경하거나 확장해야 하지만, 기존의 구조를 변경하고 싶지 않을 때 
  • 객체에 여러 기능을 조합하여 사용하고자 할 때

데코레이턴 패턴의 장점

  • 기존 객체를 변경하지 않고도 기능을 확정하거나 수정 가능할 수 있어서, 객체의 유연성을 높여줌.
  • 각각의 데코레이터는 독립적으로 재사용 가능하며, 기능을 조합하여 다양한 객체를 생성 가능. 
  • 각 데코레이터 클래스마다 고유의 책임을 가져 단일 책임 원칙(SRP)을 준수.

데코레이터 패턴의 단점

  • 데코레이터 패턴을 사용하면 객체 간의 복잡한 관계가 생김.
  • 가독성이 낮아지거나 특정 데코레이터를 제거하기 힘든 경우가 발생.   

데코레이터들을 연결할 때 순서가 중요해요!

예를 들면, 버퍼링 기능을 추가한 후에 암호화 기능을 추가하는 것과 암호화 기능을 추가한 후에 버퍼링 기능을 추가하는 것은 결과가 전혀 달라집니다. 따라서 데코레이터들을 연결할 때에는 순서를 신중하게 결정해야 해요. 🤕

 

 

데코레이터 패턴은 기존 객체에 영향을 주지 않고 새로운 기능을 추가할 수 있는 큰 장점이 있는 디자인 패턴인 거 같아요.

운영 중인 산군 서비스에 로그 저장 및 암호화 저장 부분, 등 다양한 기능을 넣어서 적용할 수 있는지 검토 중이고,

완료가 되면 그 후기를 다시 포스팅해서 돌아오겠습니다 🙋🏼‍♂️🙋🏼‍♂️


 

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

 

Examples of GoF Design Patterns in Java's core libraries

I am learning GoF Java Design Patterns and I want to see some real life examples of them. What are some good examples of these Design Patterns in Java's core libraries?

stackoverflow.com

 

Use Cases and Examples of GoF Decorator Pattern for IO

I have read in wikipedia that Decorator pattern is used for .Net and Java IO classes. Can anybody explain how this is being used? And what is the benefit of it with a possible example? There is an

stackoverflow.com

 

Design Pattern - Decorator Pattern

개요 오늘은 Decorator Pattern에 대해 알아보는 시간을 가지겠다. Decorator Pattern은 Head First Design Pattern 책의 117p ~ 145p에 설명되어 있으며 책의 내용과 구글링 정보를 종합해서 정리할 예정이다. 데코

velog.io

 

💠 데코레이터(Decorator) 패턴 - 완벽 마스터하기

Decorator Pattern 데코레이터 패턴(Decorator Pattern)은 대상 객체에 대한 기능 확장이나 변경이 필요할때 객체의 결합을 통해 서브클래싱 대신 쓸수 있는 유연한 대안 구조 패턴이다. Decorator을 해석하

inpa.tistory.com