0. 어떤 상황에 쓰는가?


  • 커피 주문 시스템

    • 기본 커피
    • 추가적인 토핑(휘핑, 모카 등)
  • 상속을 사용한다면?

    • 기본 커피 클래스
    • 모든 조합 예시에 대한 각각의 클래스가 필요
      • 모카를 추가한 카페라떼 클레스는 카페라떼를 상속받고 모카 가격을 더하는 방식
      • 새로운 토핑메뉴가 생길 경우 새로운 클래스를 작성하기위해 코드를 수정해야한다.
      • 메뉴에 없는 주문(극단적으로 카페라떼에 휘핑크림 3개, 모카 10개, 계피 4개)는 주문할 수 없다.
    • 가격이 바뀔 경우 코드의 cost() 메소드를 직접 수정해야한다.
    • 시간이 가고 메뉴가 다양화될수록 모든 클래스를 관리하기 어려워 질것이다.

only_extends


  • 코드를 수정하지 않고 동적으로 기능을 추가하고 확장하기 위한 디자인 패턴이 필요하다.
    • OCP(Open-Closed Principle)
      • 변경에는 닫혀있고 확장에는 열려있는 객체지향 디자인 원칙
      • 단순히 모든 개별적인 경우에 대해 상속을 받아야 한다면 이것을 지킬 수 없고 시시떼떼로 코드를 수정하고 클래스를 수정하는 작업이 필요하다.
    • 이를 위해서 데코레이터 패턴을 사용한다.

1. 데코레이터 패턴이란?


  • 객체에 추가적인 요건을 동적으로 추가하는 디자인 패턴

    • 코드 수정없이 새로운 기능을 추가하여 확장이 가능
    • 서브클래스를 만드는 것을 통해 기능을 확장
  • 데코레이터 객체는 상위 클래스를 감싸는 래퍼 객체(Wrapper)로 만든다.

    • 여러 번 감쌀 수 있기때문에 여러 기능을 마음대로 추가할 수 있다.

class_diagram


  • 구성요소

    • Component
      • 최상위 추상 클래스
    • ConcreteComponent
      • Component를 구현해 만든 기본 아이템(아메리카노, 에스프레소, 라떼 등)
    • Decorator
      • 추가적인 기능들의 최상위 추상 클래스
      • Component를 상속받는다
    • ConcreteDecorator
      • Decorator를 구현해 만든 여러가지 추가 기능들(휘핑크림, 모카 등)
  • 결국 모든 구성요소 클래스가 단 하나 Component의 하위 클래스들이다.

    • 데코레이터는 Component를 감싸는 객체로 정의된다.
    • 몇번을 감싸도 결국 Component의 하위 객체이기에 여러번 감쌀 수가 있다.
    • 따라서 원하는대로 구성할 수 있다.
  • 데코레이터는 감싸는 객체의 메소드를 확장한다.

    • 감싸는 객체의 메소드를 호출하면서 추가적으로 무언가를 더한다.
    • 이것이 데코레이터가 기능을 확장하는 방법이다.

wrapper


class_diagram_example


2. 예시


//Component
public abstract class Beverage {
    String description;

    public String getDescription() {
        return description;
    }

    public abstract int cost();
}

//Decorator
public abstract class Topping extends Beverage {
    Beverage beverage;
}

//ConcreteComponent
public class Americano extends Beverage {

    public Americano() {
        description = "아메리카노";
    }

    @Override
    public int cost() {
        return 2000;
    }
}

//ConcreteDecorator
public class Mocha extends Topping {

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
        description = beverage.getDescription() + ", 모카";
    }

    @Override
    public int cost() {
        return 500 + beverage.cost();
    }
}

//ConcreteDecorator
public class Whipping extends Topping {
    public Whipping(Beverage beverage) {
        this.beverage = beverage;
        description = beverage.getDescription() + ", 휘핑";
    }

    @Override
    public int cost() {
        return 800 + beverage.cost();
    }
}

//ConcreteDecorator
public class Mocha extends Topping {

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
        description = beverage.getDescription() + ", 모카";
    }

    @Override
    public int cost() {
        return 500 + beverage.cost();
    }
}

public class Main {
    public static void main(String[] args) {
        Beverage americano = new Americano();
        americano = new Mocha(americano);
        americano = new Mocha(americano);
        americano = new Whipping(americano);
        System.out.println(americano.getDescription() + ": " + americano.cost()+ "원");

        Beverage latte = new Latte();
        latte = new Whipping(latte);
        System.out.println(latte.getDescription() + ": " + latte.cost() + "원");
    }
}

실행결과

아메리카노, 모카, 모카, 휘핑: 3800원
라떼, 휘핑: 3300원

3. 특징


  • 상속을 통해 행동을 물려받을 목적보다는 형식을 맞추는 것이 포인트

    • 데코레이션 한 것도 Component 객체이기때문에 반복적인 데코레이션이 가능
  • 데코레이터 클래스에 위임된 행동 이외의 새로운 메소드를 추가해서 사용할 수 있다.

  • 자잘한 클래스들이 많이 추가되고 코드가 복잡해질 수 있다는 단점이 존재한다.

    • Java의 I/O 라이브러리
    • 단지 InputStream을 감싸는 데코레이터일 뿐이므로 복잡할 것 없다
  • 꽤 많은 데코레이터로 감싸서 초기화 해야할 경우가 생긴다.

    • 데코레이터 패턴은 일반적으로 팩토리, 빌더와 같이 사용해서 이러한 문제점을 해결한다.

4. 정리 및 생각


상속을 통해 형식을 맞춘다. 새로운 객체를 만들 때 생성자에서 같은 형식의 객체를 받아 그것을 감싸서 데코레이션을 할 수 있다. 데코레이션을 해서 나오는 객체 역시 똑같은 객체이다. 이것은 또다시 데코레이션 될 수 있단 의미이다. 이런식의 디자인을 통해 필요한 기능을 동적으로 추가해서 사용하는 일이 가능해진다. 데코레이터 패턴을 사용하면 확장에는 열려있고 변경에는 닫혀있는 OCP 객체지향 디자인 원칙이 지켜진다.
Java의 I/O 패키지도 데코레이션 패턴으로 되어있다. 기본 스트림들은 ConcreteComponent에 속하고 FilterInputStreamDecorator추상 클래스에 해당한다. FilterInputStream을 확장한 ConcreteDecorator 클래스의 예시로는 BufferedInputStream, LineNumberInputStream 등이 있다.

'소프트웨어 공학 > 디자인패턴' 카테고리의 다른 글

커맨드(Command) 패턴  (0) 2020.06.20
싱글톤(Singleton) 패턴  (0) 2020.06.20
팩토리(Factory) 패턴  (0) 2020.06.17
옵저버(Observer) 패턴  (0) 2020.06.03
스트래티지(Strategy) 패턴  (0) 2020.05.31

+ Recent posts