Computer Science/디자인패턴

[Head First Design Patterns] 03 데코레이터 패턴

계속지나가기 2020. 10. 7. 20:43
반응형

(본 강의 노트는 한빛 미디어의책을 기반으로 하고 있습니다)

03 데코레이터 패턴

Decorator Pattern

목적

  • 객체에 추가적인 책임을 동적으로 부여함.
  • 데코레이터는 서브클래싱(상속)을 사용하지 않아도 유연하고 융통성 있는 기능 확장을 가능하게 함

문제

  • 조금씩 기능을 추가하기 위해 새로운 클래스를 생성하는 경우
  1. 상속으로 문제를 풀면 너무 많은 상속 관계가 발생할 수 있음
  2. 상속을 사용하지 않고 새로운 기능을 추가할 수 있는가?

디자인 패턴 요소

요소

설명

이름

데코레이터(Decorator)

문제

조금씩 다른 다양한 종류. 늘어날수록 확장 어려움

해결방안

상속을 사용하지 않고 연관으로 필요한 기능 추가. 실행시점 확장 Extension at runtime (not compile time)

결과

확장성

데코레이터 패턴 정의

  1. 데코레이터 패턴에서는 객체에 추가적인 요건을 동적으로 첨가
  • 서브클래스를 만드는 것을 통해 기능을 유연하게 확장할 수 있는 방법을 제공
  • 상속을 사용은 하나, 무분별하게 사용하지 않고 연관으로 필요한 기능을 추가
  1. 데코레이터에서는 새로운 메소드를 추가할 수 있다
  • 일반적으로 새로운 메소드를 추가하는 대신 컴포넌트에 원래 있던 메소드 호출 전,또는 후에 별도의 작업을 처리하는 방식으로 새로운 기능을 추가
  1. 데코레이터 패턴에서의 상속은 기능을 물려 받기 위함이 아닌 형식을 맞추기 위해서임
  2. 컴포넌트는 추상 클래스 또는 인터페이스로 구현 가능
  3. 데코레이터 클래스 다이어그램

  • Component : 각 구성요소는 직접 쓰일 수도 있고, 데코레이터로 감싸져서 쓰일 수도 있음
  • ConcreteComponet : 새로운 행동을 동적으로 추가
  • Decorator는 자신이 장식할 구성요소와 같은 인터페이스 또는 추상클래스를 구현
  • ConcreteDecorator에는 그 객체가 장식하고 있는 것을 위한 인스턴스 변수가 있음
  • Decorator Component의 상태를 확장할 수 있음

사례1: 스타버즈 커피

: 스타버즈 커피는 급속도로 성장한 초대형 커피 전문점

  • 급성장하다 보니 다양한 음료들을 모두 포괄하는 주문 시스템을 갖추려고 함
  • 초기 시스템은 아래와 같음

  • 커피 주문 시, 사람에 따라 여러 토핑을 얹을 수 있음, 이 경우 가격이 토핑 별로 추가됨

  • 문제
    : 관리의 어려움 -> 즉, 상속이 남용되는 경우가 생길 수 있음
  1. 새로운 토핑이 추가된다면?
  2. 기존에 들어가는 재료의 가격이 인상된다면?
  • 멤버 변수를 사용하고 상속받는다면?
  1. Beverage의 cost()에서는 추가되는 토핑 가격의 합을 계산
  2. 자식 클래스의 cost()에서는 음료 가격 추가
  • 문제점?
  1. 새로운 토핑의 추가시 부모 클래스인 Beverage가 바뀌어야 함
  2. 불필요한 정보 포함 : 토핑+음료의 조합이 절대 가능성이 없는데 정의해두었음 (아이스티에 휘핑 추가)

  • 데코레이터 패턴 적용
    • 특정 음료에서 시작해서 첨가물로 그 음료를 장식
    • 예: 모카 + 휘핑크림을 추가한 다크로스트 커피 주문
      1. 다크로스트 객체 가져옴
      2. 모카 객체로 장식
      3. 윕 객체로 장식
      4. cost()메소드 호출(첨가물의 가격을 계산하는 일은 해당 객체들에 위임됨) : 모카, 휩 등은 Wrapper class 임

  • Beverage는 구성요소를 나타내는 컴포넌트 추상 클래스 같은 개념으로 볼 수 있음

- 코드

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

    public String getDescription() {
        return description;
    }
    public abstract double cost();
}

public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}
public class Espresso extends Beverage {
    public Espresso() {
        description = "에스프레소";
    }
    public double cost() {
        return 1.99;
    }
}
public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = "하우스 블렌드 커피";
    }
    public double cost() {
        return .89;
    }
}
public class Mocha extends CondimentDecorator {
    Beverage beverage; 
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }
    public String getDescription() {
        return beverage.getDescription() + ", 모카";
    }
    public double cost() {
        return beverage.cost() + .20;
    }
}
public class StarbuzzCoffee {
    public static void main(String[] args[]) {
        Beverage b = new Espresso();
        System.out.println(b.getDescription() 
          + " $" + b.cost());
        Beverage b2 = new DarkRoast();
        b2 = new Mocha(b2);
        b2 = new Mocha(b2); // 모카 한 개 더 추가
        b2 = new Whip(b2);
        System.out.println(b2.getDescription() 
          + " $" + b2.cost());
        Beverage b3 = new HouseBlend();
        b3 = new Soy(b3);
        b3 = new Mocha(b3);
        b3 = new Whip(b3);
        System.out.println(b3.getDescription() 
          + " $" + b3.cost());
    }
}


사례2 : 자바 I/O

  • 자바의 입출력은 입출력 패키지에서 처리하고, 다음 4개의 클래스를 중심으로 데코레이터 패턴을 사용
  • 아래 클래스들은 데코레이터의 구상요소로 쓰이며, 추상 클래스라서 직접 사용할 수 없음

    구분

    입력

    출력

    바이트

    InputStream

    OutputStream

    문자

    Reader

    Writer

  1. FileInputStream 구성요소

  2. BufferedInputStream 데코레이터

    • 입력된 내용을 버퍼에 저장

    • 입력 내용을 한 줄 씩 읽을 수 있게 readLine() 제공

  3. LineNumberInputStream 데코레이터 : 줄 번호를 붙여줌

클래스 다이어그램

반응형