[디자인 패턴] 데코레이터 패턴

이정규·2022년 6월 16일
0

정의

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

특징

  1. 데코레이터의 슈퍼클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 같습니다.
  2. 한 객체를 여러 개의 데코레이터로 감쌀 수 있습니다.
  3. 데코레이터는 자신이 감싸고 있는 객체와 같은 슈퍼클래스를 가지고 있기에 원래 객체(싸여 있는 객체)가 들어갈 자리에 데코레이터 객체를 넣어도 상관없습니다.
  4. 데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 일 말고도 추가 작업을 수행할 수 있습니다.
  5. 객체는 언제든지 감쌀 수 있으므로 실행 중에 필요한 데코레이터를 마음대로 적용할 수 있습니다.

예시

커피숍을 예시로 들어보자.

Beverage라는 인터페이스를 두고 이를 구현하는 클래스들(아메리카노, 에스프레소..) 등을 구현할 수 있다.

그렇다면 스타벅스를 생각해보자.

스타벅스에서 커피를 시킬 때 추가 옵션을 붙여서 시킬 수 있다. 이러한 부분을 어떻게 나타낼 것인가?

모두 Beverage 인터페이스를 두고 휘핑크림을 추가하고, 추가하고.. 추가하고.. 이러한 것들을 하나하나 구현시킨다면 클래스가 말 그대로 폭발할 것이다.

⇒ 바뀌는 부분과 바뀌지 않는 부분을 나누지 않았다. 이를 나눠보자.

⇒ 상속보다는 구성을 활용해보자.

그렇다면 어떻게 진행할 수 있을까?

이러한 꾸며주는 것들을 Beverage를 추상 클래스로 변환하여 속성값을 지니도록 하는 방법이 있을 것이다.

휘핑 크림, 우유, 모카등등을 속성으로 지니고 있게 만든다.

그 다음 자식 클래스에서 해당 속성을 변경하게 만들고 부모클래스에서 이를 바탕으로 가격을 계산한다.

괜찮아보인다..? 하지만 새로운 첨가물이 들어온다면?

  1. 부모 클래스에서 코드의 변경이 일어나야한다. ⇒ OCP위반
  2. 자식 클래스에서 원하지 않는 첨가물들을 강제로 상속받게 된다.

데코레이터 패턴 사용

만약 DarkRoast커피에 모카를 넣고 휘핑크림을 추가한다고 해보자.

그렇다면 DarkRoast객체를 생성하고 이를 모카 객체로 감싸고, 휘핑크림 객체로 감싸면 된다.

즉, 장식을 한다는 말이다. 그래서 데코레이터 패턴이다. 다음과 같은 구조를 가진다.

먼저 다크로스트, 아메리카노와 같은 기본적인 구현체들은 ConcreteComponent 에 담긴다.

그 다음 휘핑 크림, 간장, 설탕과 같은 꾸며주는 객체들은 Decorator 을 상속받아 ConcreteDecorator에서 구현한다.

이렇게 꾸며주는 객체와 일반 객체는 같은 슈퍼클래스를 지닌다.

코드로 한 번 봐보자.

Beverage

public abstract class Beverage {
    public String description = "";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

Beverage 기본적인 구현체

public class HouseBlend extends Beverage {

    public HouseBlend() {
        description = "하우스 블랜드 커피";
    }

    @Override
    public double cost() {
        return 0.99;
    }
}
public class DarkRoast extends Beverage {

    public DarkRoast() {
        description = "다크 로스트 커피";
    }

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

Decorator

public abstract class CondimentDecorator extends Beverage {
    Beverage beverage;

    public abstract String getDescription();
}

Decorator 구현체

public class Whip extends CondimentDecorator {

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

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

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

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

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

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

테스트

public class Main {
    public static void main(String[] args) {
        Beverage coffee = new Soy(new Whip(new DarkRoast()));
        System.out.println(coffee.cost());
        System.out.println(coffee.getDescription());
    }
}

궁금점

구상 구성 요소의 정보를 제대로 알수 없지 않나요?

HouseBlend, DarkRoast이러한 객체를 Decorator로 감싸버리면 그 다음에는 얘네가 누군지 모르잖아요. 라는 뜻이다. 맞는 말이다.

그래서 구상 구성 요소에 어떠한 작업을 해야한다면 코드가 제대로 작동하지 않을 수 있다.

이 때에는 데코레이터 패턴을 적용하는게 옳을 지 생각해봐야 한다.

참고
헤드퍼스트 디자인 패턴
https://dailyheumsi.tistory.com/198

profile
강한 백엔드 개발자가 되기 위한 여정

0개의 댓글