객체지향 및 기반 언어/디자인 패턴

[Design Pattern] Chapter 03 데코레이터 패턴

박지환 2022. 10. 1. 18:40

OCP 살펴보기

디자인 원칙 5: OCP(Open-Closed Principle) 클래스는 확장에는 열려 있어야 하지만, 변경에는 닫혀 있어야 한다.

데코레이터 패턴의 정의

데코레이터 패턴(Decorator Pattern)으로 객체에 추가 요소를 동적으로 더할 수 있다. 데코레이터를 사용하면 서브클래스를 만들 때보다 훨씬 유연하게 기능을 확장할 수 있다.

데코레이터 패턴 UML

 

데코레이터 추상 클래스는 장식할 Component(구성 요소) 인스턴스 변수를 가지고 있으면서, 자신이 장식할 구성 요소와 같은 인터페이스/추상 글래스를 구현한다.

데코레이터 패턴 예제 코드

public abstract class Beverage {
    String description = "제목 없음";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}
public class Espresso extends Beverage {

    public Espresso() {
        description = "에스프레소";
    }

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

Beverage 추상 클래스를 통해 Espresso와 같은 구상 클래스들을 만들 수 있으며, 데코레이터 클래스로 감쌀 수 있다.

public abstract class CondimentDecorator extends Beverage {
    Beverage beverage;
    public abstract String getDescription();
}
public class Mocha extends CondimentDecorator {

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", 모카";
    }

    @Override
    public double cost() {
        return beverage.cost() + .20;
    }
}
public class Whip extends CondimentDecorator {

    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", 휘핑크림";
    }

    @Override
    public double cost() {
        return beverage.cost() + .10;
    }
}

CondimentDecorator 추상 클래스는 Beverage 클래스를 상속하며 동시에 구성(Composition)으로 가지고 있다.

이를 통해 추상 클래스를 구현하는 Mocha나, Whip 구상 클래스가 Beverage의 메소드를 호출한 후 추가 작업을 진행하여 Beverage의 메소드를 오버라이드 하는 형태로 데코레이팅을 한다.

public class StarbuzzCoffee {

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

        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription() + " $" + beverage2.cost());

        Beverage beverage3 = new HouseBlend();
        beverage3 = new Soy(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Whip(beverage3);
        System.out.println(beverage3.getDescription() + " $" + beverage3.cost());
    }
}

 DarkRoast 클래스의 인스턴스인 beverage2는 Mocha와 Whip 데코레이터 클래스를 거치면서 데코레이팅된다.

이후 beverage2.getDescription()으로 호출되면 getDescription() (Whip) -> getDescription() (Mocha) -> getDescription() (Mocha) -> getDescription() (DarkRoast) 순으로 내부적으로 호출되고, 그 결과가 역순으로 반환되어 결과가 출력된다.

데코레이터가 적용된 예: 자바 I/O

java.io 패키지는 데코레이터 패턴을 바탕으로 만들어졌다. InputStream 추상 구성 요소를 구현한 FileInputStream 등의 구상 구성 요소들을 FilterInputStream이라는 추상 데코레이터를 구현하는 구상 데코레이터들이 꾸미고 있다.

데코레이터의 단점: 데코레이터를 위한 잡다한 클래스들이 많아

핵심 정리

  • 디자인의 유연성 면에서 보면 상속으로 확장하는 일은 별로 좋은 선택이 아님.
  • 기존 코드 수정 없이 행동을 확장해야 하는 상황도 있음.
  • 구성과 위임으로 실행 중에 새로운 행동을 추가할 수 있음.
  • 상속 대신 데코레이터 패턴으로 행동을 확장할 수 있음.
  • 데코레이터 패턴은 구상 구성 요소를 감싸 주는 데코레이터를 사용함.
  • 데코레이터 클래스의 형식은 그 클래스가 감싸는 클래스 형식을 반영함(상속이나 인터페이스 구현으로 자신이 감쌀 클래스와 같은 형식을 가짐)
  • 데코레이터는 자기가 감싸고 있는 구성 요소의 메소드를 호출한 결과에 새로운 기능을 더함으로써 행동을 확장함.
  • 구성 요소를 감싸는 데코레이터의 개수에는 제한이 없음.
  • 구성 요소의 클라이언트는 데코레이터의 존재를 알 수 없음. 클라이언트가 구성 요소의 구체적인 형식에 의존하는 경우는 예외임.
  • 데코레이터 패턴을 사용하면 자잘한 객체가 매우 많이 추가될 수 있고, 데코레이터를 너무 많이 사용하면 코드가 필요 이상으로 복잡해짐.

 

GitHub Code Reference: https://github.com/Krapi0314/Design_Patterns

 

GitHub - Krapi0314/Design_Patterns: Java Implementation of GoF's Design Patterns

Java Implementation of GoF's Design Patterns. Contribute to Krapi0314/Design_Patterns development by creating an account on GitHub.

github.com

Reference: http://www.yes24.com/Product/Goods/108192370

 

헤드 퍼스트 디자인 패턴 - YES24

유지관리가 편리한 객체지향 소프트웨어 만들기!“『헤드 퍼스트 디자인 패턴(개정판)』 한 권이면 충분하다.이유 1. 흥미로운 이야기와 재치 넘치는 구성이 담긴 〈헤드 퍼스트〉 시리즈! 하나

www.yes24.com