데코레이터 패턴

강한친구·2022년 4월 4일
0

OOP Desing Pattern

목록 보기
5/15

OCP

OCP는 가장 중요한 원칙가운데 하나이다.
다른글에서도 설명했지만 확장에는 open이지만, 변경에는 closed라는 뜻이다.

즉, 클래스를 확장하는것은 가능하지만, 코드를 고치는건 안된다는 뜻이다.

뭔가 모순되는 말 같지만, 옵저버 패턴에서도 본 것 처럼, 기존의 인터페이스 코드, 혹은 상위클래스 코드는 하나도 건드리지 않고 확장하는것이 가능하다. (물론 이를 적용시키려면 클라이언트 코드를 살짝 수정하긴 해야한다.)

그리고 OCP는 지키면 좋지만, 모든 코드에서 지킬 수 도 없고 안지키고 짜는게 더 나을때도 있다.

데코레이터 패턴

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

근데 이거 글로만 보니깐 좀 이해가 안간다. 백문이 불여일타라고 일단 코딩을 해보자

코드

package starbuzz;

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

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}
package starbuzz;

public abstract class CondimentDecorator extends Beverage{
    public abstract String getDescription();
}

Beverage는 Espresso같은 음료 클래스의 상위클래스이다.
CondimentDecorator는 첨가물의 상위클래스이다. Descriptor를 Beverage로부터 받아온다.

음료 예시

package starbuzz;

public class Espresso extends Beverage{
    public Espresso() {
        description = "Espresso";
    }
    @Override
    public double cost() {
        return 1.99;
    }
}

에스프레소라는 음료를 구현한 클래스이다. Beverage의 descriptor를 받아서 자신의 음료명을 전달하고 cost를 전달한다.

첨가물 예시

package starbuzz;

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

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

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

Whip은 첨가물이다. 생성자로 beverage를 받아서 beverage.cost에 자신의 값을 더해서 반환하고, 설명 역시 beverage description에 자신의 첨가물명을 첨부해서 반송한다.

실행

package starbuzz;

public class StarBuzzCoffee {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + " $" + beverage.cost());

        Beverage beverage1 = new DarkRoast();
        //System.out.println(beverage1.getDescription() + " $" + beverage1.cost());
        beverage1 = new Mocha(beverage1);
        beverage1 = new Mocha(beverage1);
        beverage1 = new Whip(beverage1);
        System.out.println(beverage1.getDescription() + " $" + beverage1.cost());

        Beverage beverage2 = new HouseBlend();
        //System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
        beverage2 = new Soy(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
    }
}
Espresso $1.99
Dark Roast, Mocha, Mocha, Whip $1.49
House Blend Coffee, Soy, Mocha, Whip $1.34

첫번째는 단순히 음료클래스만 구현을 한 경우이다.
두번째는 음료에 모카2개, 휩 1개 추가한 모습이다.

DarkRoast라는 음료를 가진 객체 beverage1에 Mocha라는 새로운 객체를 추가한다. 그러면 이는 데코레이터 형식으로 작동 DarkRoast를 감싸게되고,

내부 메소드들에 의하여 beverage의 코스트를 '자신의 코스트 + 신규 코스트'로 바꾸게 된다. 설명 역시 마찬가지로 작동한다.

이를 반복하면 결국 출력결과가 완성이 된다.

사이즈 추가

getSize, setSize를 Beverage 클래스에 추가하고 이를 통해 사이즈를 설정하고, 가격변동을 주는 기능을 추가할 수 있다.

package starbuzz;

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

    String size = "No Size";

    public String getDescription() {
        return description;
    }

    public abstract double cost();

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }
}

setSize는 외부에서 들어온 size값을 Beverage의 size로 바꿔주는 역할을 한다.
getSize는 size값을 반환해준다.

package starbuzz;

public class Mocha extends CondimentDecorator{
    Beverage beverage;

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

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

    @Override
    public String getSize() {
        return beverage.getSize();
    }

    @Override
    public double cost() {
        double cost = beverage.cost();
        if (getSize().equals("Tall") ) {
            cost += .10;
        } else if (getSize().equals("Grande")) {
            cost += .15;
        } else if (getSize().equals("Venti")) {
            cost += .20;
        }
        return cost;
    }
}

이를 활용하여 모카의 가격정책을 다르게 설정할 수 있다.

 Beverage beverage = new Espresso();
        beverage.setSize("Venti"); // 실제로는 입력값을 넣어주면 될것같다.
        System.out.println(beverage.getDescription() + beverage.getSize() + " $" + beverage.cost());
        beverage = new Mocha(beverage);
        System.out.println(beverage.getDescription() + beverage.getSize() + " $" + beverage.cost());
        //beverage.setSize("Grande");
        //System.out.println(beverage.getDescription() + beverage.getSize() + " $" + beverage.cost());

이렇게 하면

Espresso Venti $1.99
Espresso , MochaVenti $2.19

같은 결과가 나온다.
다만 문제점이 있는데
만약 사이즈의 변경이 생긴다면, 그 값이 덮어써지지 않는다는점이다.

결론

데코레이터패턴에 대해서도 공부를 했다. 뭔가 지금까지 배운 패턴들이 다 그게 그거같은데 또 미묘하게 다른 무언가가 있다. 머리로는 이해가 가는데 막상 코드를 짜보려하면 잘 짜지지 않는게 좀 어려운 부분이다.

0개의 댓글