티스토리 뷰

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

데코레이터 패턴에서는 객체에 추가적인 요건을 동적으로 첨가한다.
데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.

구성요소

1. Component

- 동적으로 기능을 추가 받을 객체의 인터페이스

2. Decorator

- Component 객체에 대한 참조를 가진다.

- Component의 인터페이스 타입을 구현한다. (같은 타입을 가진다)

3. ConcreteComponent

- 기능이 추가 받을 수는 객체

4. ConcreteDecorator

- Component에 기능을 더한다.

 

기본이 되는 디자인 원칙

OCP(Open-Closed Principle): 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.

 

2. 동기

클래스/인터페이스가 아닌 특정 객체에만 기능을 추가하고 싶을 때가 있다.
즉 기능 추가라는 확장은 하면서 기존 구조 (클래스)를 변경하는 일을 피하고 싶을 때 쓸 수 있는 디자인 패턴이다.

 

장점:

1) 정적 상속보다 더 유연하다.

- 그냥 상속을 하면 상위 클래스의 모든 것을 이어 받게 된다. 데코레이터를 쓰면 런타임에서 기능을 추가/분리를 할 수 있게 된다.

- 상속을 사용하게 되면 추가되는 기능마다 경우의 수가 늘어난다. 하지만 데코레이터는 기능 하나 당 한 클래스만 필요하다.

 

2) 필요한 기능 추가를 뒤로 미룰 수 있다.

- 추가할 기능과 그 기능을 가질 객체들이 분리가 되어있기 때문에, 상위 계층의 클래스 설계 시 어떤 기능이 필요할지 미리 생각할 필요가 없다.

 

단점:

1) 객체에 대한 비교를 할 수 없다.

- decorator로 component를 감싸는 순간, 더 이상 기능이 추가되기 전 객체가 아니게 된다.

- 따라서 equals 등 객체를 비교하는 로직을 사용할 수 없게 된다.

 

2) 자잘한 객체들이 많이 생겨 구조가 복잡해진다.

- 비슷하게 생긴 객체들이 많아진다.

- 여러 객체들이 인스턴스 변수나 어떤 값으로 정의되는게 아니라 참조로 인해 정의되기 때문에 처음보는 사람은 이해하기 매우 어려운 구조가 될 수 있다.

 

3. 적용 시 추가적으로 고려해야 될 것들

1) Component 클래스는 최대한 가볍게

- Component에 인스턴스 변수를 가지는 걸 피하는 것이 좋다.

- Component은 인터페이스를 정의하는 것에 초점을 두는 것이지, 데이터를 저장하는 역할을 하는 것이 아니다.

- 데이터를 저장하는 행위는 결국 기능과 연관이 되어있기 때문에 그 기능은 ConcreteDecorator들한테 위임하자.

 

2) 가능하다면 바로 ConcreteDecorator 사용

- 추가될 기능이 한가지라면 굳이 Decorator -> ConcreteDecorator 형식을 지킬 필요가 없다.
- 이런 경우는 주로 이미 클래스끼리의 계층구조가 정의되어 있을 때가 많다.

 

3) 데코레이터 VS 스트래티지

- 데코레이터는 껍데기를 바꾸는 것 VS 스트래티지는 알맹이를 바꾸는 것

 

4. 개인적인 포인트

1) 클래스가 아닌 객체를 타게팅해서 추가적인 작업을 하고 싶을 때 <- 이게 포인트 같다.

 

2) Gof Design Patterns 책에서 데코레이터의 장점 중 기능을 런타임에 탈부착(위에서는 추가/분리라고 함)할 수 있다고 했다.

새로운 기능을 추가하는 거는 데코레이터 클래스로 감싸면 되는 건 알겠는데, 분리는 어떻게 하는 것인가?

 

해결책을 찾아보니 되게 별로인데, 요약하자면 기존의 기능을 삭제하는 Decorator를 Wrapping 하는 것이다...

 

익숙한 커피 예시를 들어보면:

interface Coffee {
    double cost();
    String description();
}

class SimpleCoffee implements Coffee {
    public double cost() {
        return 2.0;
    }

    public String description() {
        return "Simple Coffee";
    }
}

class MilkDecorator implements Coffee {
    private Coffee coffee;

    public MilkDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    public double cost() {
        return coffee.cost() + 1.0;
    }

    public String description() {
        return coffee.description() + ", Milk";
    }
}

라떼(커피 + 밀크)를 만들고 싶으면 이렇게 생성하면 될 것이다.

Coffee latte = new Coffee();
latte = new MilkDecorator(latte);

여기서 MilkDecorator만 빼려면 어떻게 할까?

class NoMilkDecorator implements Coffee {
    private Coffee coffee;

    public NoMilkDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    public double cost() {
        return coffee.cost() - 1.0; // Counteracts the MilkDecorator
    }

    public String description() {
        return coffee.description().replace(", Milk", ""); // Removes "Milk" from the description
    }
}

...

latte = new NoMilkDecorator(latte);

이런 짓을 해야 된다는 거다... 분리는 없다고 생각하고 만드는게 좋을 듯 하다.