블링블링 범블링

데코레이터 패턴(Decorator Pattern) 본문

Technology/객체 지향 디자인 패턴

데코레이터 패턴(Decorator Pattern)

뻠스키 2018. 4. 18. 11:32

데코레이터 패턴(Decorator Pattern)



데코레이터 패턴은 객체에게 추가적인 요건을 동적으로 첨가할 수 있는 것을 말한다. 데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있다. 데코레이터 말 그대로 무언가 첨가되고, 꾸며주는 구현을 말한다.  


위 그림에서는 데코레이터 패턴의 기본 클래스다이어그램을 보여준다. 단순히 이것만 봐서는 곧바로 이해가 되지 않기 때문에 예시를 통해서 이해하는 게 좋다고 생각한다.



보통 기능의 확장을 할 때 상속을 이용하지만 다양한 조합의 기능의 확장이 요구될 때는 클래스가 불필요하게 증가하는 문제가 발생한다. 이에 대한 예시로 커피를 주문할 때 휘핑크림이나, 우유를 첨가할 수도있고, 시럽도 추가가 가능하다. 그리고 포장여부까지 더해지면 점점 기능이 복잡해지고, 계층적 구조가 많아진다. 


이러한 경우에 해결할 수 있는 방법이 데코레이터 패턴이다. 상속이 아닌 위임을 하는 방식으로 기능을 확장시킬 수 있다. 

다음 그림은 데코레이터 패턴을 적용해서 클래스 다이어그램이다. 인터페이스가 아닌 추상클래스를 통해서 구현을 했다. component는 Beverage 추상클래스가 되고, 기본 음료의 종류들이 concrete component가 된다. 그리고 커피에 첨가하고 싶은 것은 decorator 추상클래스에 위임한다. 각종 시럽들은 concrete decorator가 된다.



클라이언트에서 커피를 주문할 때 추가하고 싶은 객체를 추가하면 된다. 코드에서 CoffeeStore클래스를 보면 주문한 재료들이 추가할 수록 붙고, 가격도 달라지는 것을 확인할 수 있다.


[소스코드]


[Beverage Class : Component]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class Beverage {
 
    protected String coffeeGredient;
     
    public Beverage() {
        this.coffeeGredient ="음료";
    }
     
    public String getGredient() {
        return this.coffeeGredient;
    }
 
    public abstract int getCost();
}

[Coffee Class : Concrete Component]

1
2
3
4
5
6
7
8
9
10
11
12
public class Coffee extends Beverage{
     
    public Coffee() {
        this.coffeeGredient = "커피";
    }
 
    @Override
    public int getCost() {
        return 3000;
    }
 
}

[Tea Class : Concrete Component]

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Tea extends Beverage {
 
    public Tea() {
        this.coffeeGredient = "차";
     
    }
     
    @Override
    public int getCost() {
        return 2000;
    }
 
}

[coffeeDecorator Class : Decorator]

1
2
3
4
public abstract class coffeeDecorator extends Beverage {
     
    public abstract String getGredient();
}

[Milk Class : Concrete Decorator]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Milk extends coffeeDecorator{
     
 
    private Beverage beverage;
     
    public Milk(Beverage beverage) {
        this.beverage =  beverage;
    }
     
    @Override
    public int getCost() {
        return 1000 +  beverage.getCost();
    }
 
    @Override
    public String getGredient() {
        return  beverage.getGredient() + ", 우유 추가";
    }
 
}

[Vanilla Class : Concrete Decorator]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Vanilla extends coffeeDecorator {
     
    private Beverage  beverage;
     
    public Vanilla(Beverage  beverage) {
        this.beverage =  beverage;
    }
     
    @Override
    public int getCost() {
        return 500 + beverage.getCost();
    }
 
    @Override
    public String getGredient() {
        return  beverage.getGredient() + ", 바닐라추가";
    }
 
}

[Hazelnut Class : Concrete Decorator]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Hazelnut extends coffeeDecorator{
    private Beverage coffee;
     
    public Hazelnut(Beverage coffee) {
        this.coffee = coffee;
    }
 
    @Override
    public int getCost() {
        return 300 + coffee.getCost();
    }
 
    @Override
    public String getGredient() {
        return coffee.getGredient() + ", 헤이즐넛추가";
    }
 
}

[CoffeeStore : Client]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class CoffeeStore {
     
    public static void main(String[] args) {
         
        Beverage coffee = new Coffee();
         
        System.out.println("재료 : " + coffee.getGredient());
        System.out.println("가격 : " + coffee.getCost());
         
        coffee = new Vanilla(coffee);
        System.out.println("재료 : " + coffee.getGredient());
        System.out.println("가격 : " + coffee.getCost());
         
        coffee = new Milk(coffee);
        System.out.println("재료 : " + coffee.getGredient());
        System.out.println("가격 : " + coffee.getCost());
         
 
        Beverage tea = new Tea();
        System.out.println("재료 : " + tea.getGredient());
        System.out.println("가격 : " + tea.getCost());
         
        tea = new Milk(tea);
        System.out.println("재료 : " + tea.getGredient());
        System.out.println("가격 : " + tea.getCost());
         
        tea = new Hazelnut(tea);
        System.out.println("재료 : " + tea.getGredient());
        System.out.println("가격 : " + tea.getCost());
         
    }
}


[출력화면]


출력화면을 보면 커피(component)에서 추가하고 싶은 것을 객체 호출만으로 변경할 수 있는 것을 볼 수 있다. 상위클래스인 coffeeDecorator를 통해서 재료와 가격추가를 위임하고있다.


데코레이터 패턴은 조합을 하는 방식으로 기능을 확장해야하는 경우에 자주 쓰인다. 확장 기능이 별도의 클래스로 분리되어서 확장 기능과 원래의 기능이 분리되어 서로의 영향없이 변경이 가능하다. 

커피나 차의 클래스가 변경이되어도 데코레이터 클래스에 영향이 없고, 각각 한 가지의 책임만 가지고 있다. 이러한 점 때문에 데코레이터패턴은 객체지향의 원칙에서 단일책임원칙이 잘 이뤄져 있다는 것을 알 수 있다. 

하지만 데코레이터 패턴 사용 시에 주의해야할 사항은 본 예시는 재료와 가격을 나타낸 것으로 간단하게 구현해볼 수 있지만 데코레이터에 더 많은 메서드가 증가할수록 코드가 복잡해진다는 점이 있다.


참고 위키디피아 블로그

출처 : http://meylady.tistory.com/

Comments