개발/자바

[java] 데코레이터(Decorator) 패턴

조용한닭 2024. 1. 11. 13:52
728x90
반응형

정의

데코레이터 패턴은 객체의 구조를 변경하지 않고도 객체에 동적으로 새로운 기능을 추가할 수 있는 구조적인 패턴입니다. 이 패턴은 객체의 확장을 위해 상속 대신 구성을 활용하며, 객체를 래핑하여 런타임 시에 새로운 기능을 추가하거나 수정할 수 있도록 합니다.

장점

  1. 개방-폐쇄 원칙(OCP, Open-Closed Principle)을 따릅니다. 기존 코드 수정 없이 새로운 기능을 추가할 수 있습니다.
  2. 단일 책임 원칙(SRP, Single Responsibility Principle)을 지킬 수 있습니다. 각 데코레이터 클래스는 자체적으로 한 가지 기능만 추가합니다.
  3. 객체 간의 결합도를 낮춥니다. 구성을 통해 필요한 데코레이터를 동적으로 추가하므로 객체 간의 의존성이 줄어듭니다.

단점

  1. 많은 데코레이터가 중첩되면 코드가 복잡해질 수 있습니다.
  2. 잘못 사용할 경우, 무한히 많은 데코레이터를 추가할 수 있으므로 주의가 필요합니다.

사용

데코레이터(Decorator) 패턴은 다양한 소프트웨어 개발 시나리오에서 사용됩니다. 주로 다음과 같은 상황에서 적용됩니다:

  1. GUI 컴포넌트 디자인: 그래픽 사용자 인터페이스(GUI) 라이브러리에서 데코레이터 패턴은 버튼, 텍스트 상자, 팝업 창 등의 GUI 컴포넌트에 추가적인 기능(예: 스크롤바, 툴팁, 드래그 앤 드롭 기능)을 동적으로 추가할 때 사용됩니다.
  2. 스트림 처리: 자바 I/O 라이브러리에서 데코레이터 패턴은 데이터 스트림(예: 파일 스트림, 버퍼 스트림)에 여러 기능(예: 압축, 암호화, 버퍼링)을 추가하기 위해 사용됩니다.
  3. 웹 애플리케이션 필터: 웹 애플리케이션에서 데코레이터 패턴은 요청과 응답 객체에 추가적인 기능(예: 인증, 로깅, 캐싱)을 동적으로 적용하는 데 사용됩니다.
  4. 커피 주문 시스템: 위에서 예시로 든 커피 주문 시스템은 데코레이터 패턴을 사용한 전형적인 예시입니다. 커피에 여러 토핑(예: 시럽, 휘핑 크림, 우유)을 추가하고 가격을 계산할 때 데코레이터 패턴을 활용할 수 있습니다.
  5. 게임 엔진: 게임 엔진에서 데코레이터 패턴은 게임 캐릭터에 다양한 능력(예: 무기, 방어구, 특수 능력)을 동적으로 부여할 때 사용됩니다.
  6. 텍스트 및 그래픽 편집기: 텍스트 에디터나 그래픽 편집기에서 데코레이터 패턴은 텍스트 스타일링(글꼴, 색상)이나 그래픽 요소(테두리, 그림 삽입) 추가에 활용됩니다.
  7. 로그 및 트래킹 시스템: 로깅 및 트래킹 시스템에서 데코레이터 패턴은 로그 항목에 추가 정보(예: 타임스탬프, 레벨, 예외 정보)를 동적으로 추가할 때 사용됩니다.

데코레이터 패턴은 객체 지향 설계 원칙을 따르고 기존 코드를 변경하지 않고 새로운 기능을 도입할 수 있도록 하는데 유용한 디자인 패턴입니다. 이러한 상황에서 데코레이터 패턴을 적용하면 코드의 확장성과 유지보수성을 향상시킬 수 있습니다.

 
 
 

자바 예시 코드

이해를 돕기 위해 커피 주문 시럽(Syrup)을 추가하는 예시를 통해 데코레이터 패턴을 설명하겠습니다.

// Component 인터페이스: 기본 커피 주문을 나타내는 인터페이스
interface Coffee {
    String getDescription();
    double cost();
}

// ConcreteComponent 클래스: 기본 커피 주문을 구현한 클래스
class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Simple Coffee";
    }

    @Override
    public double cost() {
        return 2.0;
    }
}

// Decorator 추상 클래스: 데코레이터 클래스의 기본 스켈레톤 구현
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }

    @Override
    public double cost() {
        return decoratedCoffee.cost();
    }
}

// ConcreteDecorator 클래스: 시럽을 추가하는 데코레이터 클래스
class SyrupDecorator extends CoffeeDecorator {
    public SyrupDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", with Syrup";
    }

    @Override
    public double cost() {
        return super.cost() + 1.0;
    }
}

public class Main {
    public static void main(String[] args) {
        // 기본 커피 주문
        Coffee coffee = new SimpleCoffee();
        System.out.println("Description: " + coffee.getDescription());
        System.out.println("Cost: $" + coffee.cost());

        // 시럽 추가된 커피 주문
        Coffee syrupCoffee = new SyrupDecorator(new SimpleCoffee());
        System.out.println("Description: " + syrupCoffee.getDescription());
        System.out.println("Cost: $" + syrupCoffee.cost());
    }
}

코드 설명

  1. Coffee 인터페이스는 커피 주문을 나타내는 메서드를 정의합니다.
  2. SimpleCoffee 클래스는 기본 커피 주문을 구현합니다.
  3. CoffeeDecorator 추상 클래스는 데코레이터 클래스의 스켈레톤을 제공합니다. 이 클래스는 Coffee 인터페이스를 구현하며 데코레이터 클래스의 기본 동작을 정의합니다.
  4. SyrupDecorator 클래스는 시럽을 추가하는 구체적인 데코레이터 클래스입니다. 생성자에서 기존 커피를 받아서 그 위에 시럽을 추가합니다.
  5. Main 클래스에서 기본 커피 주문과 시럽 추가된 커피 주문을 생성하고 출력합니다.

데코레이터 패턴을 사용하면 커피 객체를 래핑하여 동적으로 새로운 토핑(데코레이터)을 추가할 수 있습니다. 이로써 코드는 개방-폐쇄 원칙을 준수하며 유지보수와 확장성이 향상됩니다.

 

 

 
728x90
반응형