데코레이터 패턴(Decorator Pattern)

하루키·2024년 6월 30일

DesignPattern

목록 보기
3/7
post-thumbnail

📖 데코레이터 패턴(Decorator Pattern) 이란?

데코레이터 패턴(Decorator Pattern)은 객체의 기능을 동적으로 확장할 수 있도록 설계된 구조적 디자인 패턴이다.
객체에 추가적인 책임을 부여하는 데 있어서 상속보다 유연한 대안을 제공한다.
데코레이터 패턴은 객체를 감싸는(decorate) 여러 개의 데코레이터 객체를 통해 기능을 추가하거나 변경할 수 있다


유스케이스

설명
문제StarBuzz 커피숍에서, 고객은 음료를 주문할 수 있으며, 각종 옵션을 추가할 수 있어야 한다.
해결책기본 음료를 컴포넌트로 정의하고, 옵션을 데코레이터로 정의하여 음료 객체를 동적으로 꾸며준다.


⚠ 문제점


📌 상속을 이용하여 구현하였을때의 문제점

  • 첨가물 가격 변동 시 Beverag 클래스 수정 必
  • 첨가물 종류가 많아지면 Beverage 클래스에 새로운 메서드 추가 必
  • Tea와 같은 음료 출시 될 때 필요없는 메서드 상속됨
  • 동일 첨가물의 두번 추가 불가


디자인 원칙

OCP(Open-Closed Principle) : 확장에는 열려 있고, 수정에는 닫혀 있는 코드


💬 UML 및 동작 원리

데코레이트 UML

📌 상속 구조, 전체와 부분

  • Component
    🚩 Component : 요소의 공통 인터페이스나 추상 클래스
    🚩 ConcreteComponent : 기본적인 객체로, 데코레이터에 의해 감싸질 대상
    ⚠ 컴포넌트는 ConcreteComponent, Decorator, ConcreteDecorator 일 수 있음

  • Decorator
    🚩 Decorator : 컴포넌트 인터페이스를 구현하거나 상속하는 추상클래스
    🚩 ConcreteDecorator : 데코레이터의 구체적인 구현으로, 기본 객체나 다른 데코레이터를 감싸서 추가적인 기능을 제공한다.
    ⚠ 데코레이터는 Component를 여러개 가질 수 있음 (전체와 부분) → 연관

    🔔 데코레이터는 부모클래스(컴포넌트)의 매개변수(객체)를 생성자에서 받아서 꾸며준다!!



스타버즈 개선 UML

  • ConcreteDecorator 들은 Beverage에 대한 정보를 알고 있어야 함
    📌 Milk또는 Mocha의 생성자에서 beverage를 매개변수로 받아서 초기화 시켜주는 코드 有
    연관이 있는 경우는 생성자에서 초기화 해줘야 함 (해당 객체에 대한 정보)
    public Mocha(Beverage beverage) {
          this.beverage = beverage;
      }

⚠ 데코레이터는 기본 객체나 다른 데코레이터 객체를 감싸서 추가 기능을 제공하기 때문에, 데코레이터 객체를 생성할 때 반드시 감쌀 객체를 제공해야 함
→ 부모 클래스(컴포넌트)에 대한 매개변수를 데코레이터의 생성자에서 받아, 추가적으로 꾸며준다!

Beverage b1 = new Milk();Milk 가 Decorator 라서 불가
Beverage b1 = new Espresso();
b1 = new Milk(b1); → 가능

💡 데코레이터 패턴에서 실제 호출은 가장 안쪽에 있는 기본 객체부터 시작한다. 데코레이터 객체들은 내부에 있는 객체(또는 또 다른 데코레이터)를 호출하고, 그 결과에 자신의 기능을 추가함



📃 코드

테스트 코드

  public class StarBuzz {

  public static void main(String[] args) {
        Beverage b1 = new HouseBlend();
        System.out.println(b1.getDescription() + " $" + b1.cost());

        b1 = new Milk(b1); 
        System.out.println(b1.getDescription() + " $" + b1.cost());

        b1 = new Mocha(b1);
        System.out.println(b1.getDescription() + " $" + b1.cost());
        
        b1 = new Mocha(b1);
        System.out.println(b1.getDescription() + " $" + b1.cost());
    }
}

실행 결과

House Blend Coffee $0.89
House Blend Coffee, Milk $0.99
House Blend Coffee, Milk, Mocha $1.19
House Blend Coffee, Milk, Mocha, Mocha $1.39

💡 모든 객체가 Beverage 인터페이스나 추상 클래스를 구현하거나 상속받기 때문에, b1을 통해 getDescription()cost() 메서드를 호출할 수 있음!

💡 Beverage를 꾸며주는 요소(데코레이터) 들이 동일한 인터페이스를 구현하거나 동일한 추상 클래스를 상속받고 있기 때문에, 데코레이터와 실제 객체를 같은 타입으로 사용할 수 있게 된다!!



📖 java.io 클래스

java.io 클래스는 데코레이터 패턴을 적용한 대표적인 라이브러리 이다!

  • Java API 문서의 InputStreamFileInputStream을 보면, FileInputStream의 생성자에는 File 과 String 타입의 매개변수만 받음. InputStream과 관련된 항목이 없다,
    InputStream를 받는 매개변수가 없으므로, FileInputStream은 Concrete Component 이다!
    → component를 매개변수로 받으면, Decorator 이므로
  • Java API 문서의 InputStreamFilterInputStream을 보면, FilterInputStream의 생성자에는 InputStream 타입 매개변수(컴포넌트) 를 받는 것을 확인할 수 있으므로, Decorator에 해당한다!

BufferedReader

평소 입력에 자주 사용되는 BufferedReader도 데코레이터 패턴을 사용한 대표적인 예시이다!

BufferedReader br = new BufferedReader(new InputStreamReader(System.in)));

🚩 Component : Reader 클래스. 문자 스트림을 읽기 위한 공통 메서드를 정의
🚩 ConcreteComponent : InputStreamReader 클래스. 바이트 스트림을 문자 스트림으로 변환. Reader의 구체적인 구현체
🚩 ConcreteDecorator : BufferedReader 클래스. 추가적인 버퍼링 기능을 제공하여 읽기 성능 향상

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        // System.in은 InputStream으로, 바이트 스트림을 제공
        InputStream inputStream = System.in;

        // InputStreamReader는 바이트 스트림을 문자 스트림으로 변환
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream); // ConcreteComponent

        // BufferedReader는 문자 스트림에 버퍼링 기능을 추가
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader); //ConcreteDecorator

        // 사용 예시: 한 줄을 입력 받아 출력
        System.out.println("Enter a line:");
        String line = bufferedReader.readLine();
        System.out.println("You entered: " + line);
    }
}
profile
코딩 못하는 개발자(진)

0개의 댓글