S E P H ' S

[구조 패턴] 데코레이터(Decorator) 패턴 본문

Programing & Coding/Design Pattern

[구조 패턴] 데코레이터(Decorator) 패턴

yoseph0310 2023. 3. 26. 15:46

데코레이터(Decorator) 패턴

데코레이터 패턴이란?

 

데코레이터 패턴은 상속(Inheritance)과 합성(Composition)을 사용하여 객체에 동적으로 책임을 추가할 수 있게 한다. 이 방법은 서브 클래스를 생성하는 것보다 유연한 방법을 제공한다. 쉽게 말해 데코레이터라는 말처럼 장식을 추가해 나간다고 생각하면 된다. 기본 기능을 가지고 있는 클래스를 하나 만들고 추가 기능들을 추가하기 쉽도록 하는 방식이다.

 

 

언제 사용할까?

 

  • 클래스의 요소들을 계속하여 수정하면서 사용하는 구조가 필요한 경우
  • 여러 요소들을 조합해서 사용하는 클래스 구조인 경우

예를 들어 커피를 제조한다고 한다면 종류마다 다양한 재료의 구성으로 하나의 커피가 완성된다. 이 재료들을 모두 클래스로 구현한다면 굉장히 많은 클래스를 구현해야 할 것이다. 그러므로 굉장히 비효율적일 것이다. 새로운 커피를 개발할 때 마다 그 커피에 들어가는 재료의 객체를 만들고 기능을 추가해야 하기 때문이다. 종류가 많아질수록 코드가 더욱더 비효율적이게 될 것이다. 이를 해결하기 위해 기본 커피의 재료인 에스프레소라는 틀을 추상적으로 가지고 커피에 들어가는 각 재료들을 장식하는 방식을 사용한다.

 

 

구조

  • Component : 실질적인 인스턴스를 컨트롤하는 역할
  • ConcreteComponent : Component의 실질적인 인스턴스의 부분으로 책임의 주체의 역할
  • Decorator : Component 와 ConcreteDecorator를 동일시 해도록 해주는 역할
  • ConcreteDecorator : 실질적인 장식 인스턴스 및 정의이며 추가된 책임의 주체

 

장단점

 

장점

  • 기존 코드를 수정하지 않고도 데코레이터 패턴을 통해 행동을 확장시킬 수 있다.
  • 구성과 위임을 통해 실행중에 새로운 행동을 추가할 수 있다.

 

단점

  • 의미 없는 객체들이 너무 많이 추가될 수 있다.
  • 데코레이터를 너무 많이 사용하면 코드가 필요 이상으로 복잡해질 수 있다.

 

 

예시

 

Component에는 재료를 추가하는 메소드를 구현한다.

public interface Component {
    String add();   // 재료추가
}

 

Component를 구현한 BaseComponent에서는 커피의 기본 재료가 되는 에스프레소를 넣는 것으로 정의한다.

 

public class BaseComponent implements Component {

    @Override
    public String add() {
        return "에스프레소";
    }
}

 

기본재료인 에스프레소에 재료들을 장식해줄 Decorator를 추상 클래스로 만들고 Component를 구현한다. 전달받은 coffeeComponent에 따라 다른 재료를 추가할 수 있도록 이 Decorator 클래스를 상속받은 클래스들에서 만들어 줄 것이다.

 

abstract public class Decorator implements Component{
    private Component coffeeComponent;

    public Decorator(Component coffeeComponent) {
        this.coffeeComponent = coffeeComponent;
    }

    public String add() {
        return coffeeComponent.add();
    }
}

 

간단하게 물과 우유를 추가하는 데코레이터 구현체들을 만든다.

 

public class MilkDecorator extends Decorator {
    public MilkDecorator(Component coffeeComponent) {
        super(coffeeComponent);
    }

    @Override
    public String add() {
        return super.add() + " + 우유";
    }
}

 

public class WaterDecorator extends Decorator {
    public WaterDecorator(Component coffeeComponent) {
        super(coffeeComponent);
    }

    @Override
    public String add() {
        return super.add() + " + 물";
    }
}

 

 

 

테스트

 

public class DecoratorTest {
    public static void main(String[] args) {
        Component espresso = new BaseComponent();
        System.out.println("에스프레소 : " + espresso.add());

        Component americano = new WaterDecorator(new BaseComponent());
        System.out.println("아메리카노 : " + americano.add());

        Component latte = new MilkDecorator(new BaseComponent());
        System.out.println("라떼 : " + latte.add());
    }
}

 

데코레이터 패턴을 사용하여 위와 같이 자신이 감싸고 있는 구성요소의 메소드를 호출한 결과에 새로운 기능을 더해 행동을 확장할 수 있다.

 

Component woowakCoffee = new WaterDecorator(new MilkDecorator(new BaseComponent()));
System.out.println("우왁 커피 : " + woowakCoffee.add());

조금 이상한 이름의 커피이지만 어쨌든 이런식으로 만들어둔 재료 데코레이터를 사용하여 다양한 조합을 만들어낼 수 있다. 다른말로 간단하게 기본이 되는 틀위에 기능을 추가시킬 수 있다는 것이다.

 

다시한번 정리하자면 코드의 수정이 없이도 확장에는 용이하나 코드의 복잡도가 올라갈 수 있다는 것이 단점이다.