본문 바로가기

디자인패턴

Decorator Pattern

Decorator Pattern 이란?

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

 

Decorator Pattern 의 특징

  • 데코레이터의 수퍼 클래스는 자신이 장식하고 있는 객체의 수퍼클래스와 같습니다.
  • 한 객체를 여러 개의 데코레이터로 감쌀 수 있습니다.
  • 데코레이터는 자신이 감싸고 있는 객체와 같은 수퍼클래스를 가지고 있기 때문에 원래 객체가 들어갈 자리에 데코레이터 객체를 집어넣어도 상관 없습니다.
  • 데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 것 외에 원하는 추가적인 작업을 수행할 수 있습니다.
  • 객체는 언제든지 감쌀 수 있기 때문에 실행중에 필요한 데코레이터를 마음대로 적용할 수 있습니다.

 

클래스 다이어그램

  • ConcreteComponent에 새로운 행동을 동적으로 추가하게 됩니다.
  • Decorator는 자신이 장식할 구성요소와 같은 인터페이스 또는 추상 클래스를 구현합니다.
  • ConcreteDecorator에는 그 객체가 장식하고 있는 것(데코레이터가 감싸고 있는 Component 객체)을 위한 인스턴스 변수가 있습니다.
  • ConcreteDecorator는 Component의 상태를 확장할 수 있습니다.
  • ConcreteDecorator 에서 새로운 메소드를 추가할 수도 있습니다. 하지만 일반적으로 새로운 메소드를 추가하는 대신 Component에 워낼 있던 메소드를 호출하기 전, 또는 후에 별도의 작업을 처리하는 방식으로 새로운 기능을 추가합니다.

 

코드 예시

Component를 만듭니다.

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

    public String getDescription(){
        return description;
    }

    public abstract int cost();
}

 

튜닝을 나타내는 추상 클래스(데코레이터 클래스)를 구현합니다.

public abstract class TuningDecorator extends Vehicle {
    public abstract String getDescription();
}

 

ConcreteComponent (실제 탈 것)을 구현합니다.

public class SportsCar extends Vehicle {

    public SportsCar(){
        description = "스포츠카";

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

 

public class Truck extends Vehicle {

    public Truck(){
        description = "트럭";
    }

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

 

이제 ConcreteDecorator를 만듭니다.

public class HeadLamp extends TuningDecorator {
    Vehicle vehicle;

    public HeadLamp(Vehicle vehicle) {
        this.vehicle = vehicle;
    }

    @Override
    public int cost() {
        return 12 + vehicle.cost();
    }

    @Override
    public String getDescription() {
        return vehicle.getDescription() +" + 해드램프 튜닝";
    }
}

 

public class Wheel extends TuningDecorator {
    Vehicle vehicle;

    public Wheel(Vehicle vehicle){
        this.vehicle = vehicle;
    }

    @Override
    public int cost() {
        return 66 + vehicle.cost();
    }

    @Override
    public String getDescription() {
        return vehicle.getDescription()+" + 휠 튜닝" ;
    }
}
  • 해드램프와 휠은 데코레이터기 때문에 TuningDecorator를 확장합니다.
  • 데코레이터의 인스턴스에는 Vehicle에 대한 래퍼런스가 들어있습니다.
    1. 감싸고자 하는 탈 것을 저장하기 위한 인스턴스 변수
    2. 인스턴스 변수를 감싸고자 하는 객체로 설정하기 위한 생성자. 데코레이터의 생성자에 감싸고자하는 탈 것 객체를 전달하는 방식을 사용합니다.

 

실행 코드

public class Test {
    public static void main(String[] args) {
        Vehicle truck = new Truck();
        System.out.println(truck.getDescription() +", $"+ truck.cost());

        Vehicle sportsCar = new SportsCar();
        sportsCar = new HeadLamp(sportsCar);
        sportsCar = new Wheel(sportsCar);
        System.out.println(sportsCar.getDescription()+", $"+ sportsCar.cost());
    }
}

실행 결과

트럭, $50
스포츠카 + 해드램프 튜닝 + 휠 튜닝, $178

 

 

구현할 때 고려할 점

  • Component는 장식을 추가할 베이스가 되는 역할이므로 작고 가볍게 정의하도록 합니다.
    • 가급적 인터페이스만을 정의합니다.
    • 무언가 저장하는 변수는 정의하지 않습니다(상속받는 여러 Decorator도 같이 복잡하고 무거워짐).
  • 상속 구조를 통해 Decorator와 Component가 같은 인터페이스를 갖게 해야 합니다.
    • Decorator로 계속해서 감싸도 Component의 메소드를 계속 사용할 수 있습니다.
  • 코드를 수정하지 않고도 준비된 Decorator을 조합해 기능을 추가할 수 있도록 생각해서 구현합니다.
  • 비슷한 성질의 작은 클래스가 많이 만들어질 수 있다는 단점을 고려합니다.
  • 구현하려는 내용이 객체의 겉을 변경하려는 것인지, 속을 변경하려는 것인지 생각해야 합니다.
    • 속을 변경하려는 것이라면 전략 패턴(strategy pattern)을 선택하는 것이 더 적절할 수 있습니다.
  • 데코레이터 패턴으로 구현한 다음, 사용이 까다롭게 느껴지거나 자주 쓰는 조합이 있다면 빌더 패턴 등(생성 패턴)을 사용해 제공하는 것을 고려해 봅니다.
  • Decorator가 다른 Decorator에 대해 알아야 할 필요가 있다면, 데코레이터 패턴의 사용 의도에 맞지 않습니다.
  • 재귀적으로 기능을 갖게 하는 방법 외에, Decorator를 추가할 때마다 얻은 아이템을 List로 관리하는 방법도 있습니다.

 

 

참고

https://johngrib.github.io/wiki/decorator-pattern/
https://gmlwjd9405.github.io/2018/07/09/decorator-pattern.html

'디자인패턴' 카테고리의 다른 글

Observer Pattern  (0) 2021.08.07
디자인 패턴 23가지  (0) 2021.08.05