CS

[Design Patterns] Decorator Pattern

ju_young 2024. 5. 30. 15:56
728x90

음료(Beverage)라는 추상 클래스에 여러 메뉴들을 서브 클래스로 구현한다고 하면 다음과 같이 HouseBlend, DarkRoast, Decaf, Espresso 등을 추가할 수 있다.

 

 

하지만 스팀 우유나 두유, 모카와 같은 첨가물이 추가되면 메뉴가 달라지고 추가해야하는 클래스가 무수히 많아지게된다. 추가로 커피 가격까지 달라지게된다.

 

이와 같은 문제를 해결하기위해서 Beverage 클래스에 첨가물에 대한 정보를 추가해주고 주 메뉴들만 서브 클래스로 구현해주도록 할 수 있다.

하지만 위와 같이 구현해주면 첨가물의 가격이 변경되었을 경우 기존 코드를 수정해야한다는 문제와 첨가물의 종류가 추가되면 메소드들도 추가/수정되어야될 수 있다. 그리고 만약 아이스티와 같이 메뉴가 추가될 경우에 whip이 필요없음에도 무조건 상속을 받게된다.

 

이러한 문제를 OCP(Open-Closed Principle)을 위반했다고 말한다. 즉, 클래스는 확장에 대해서는 열려있어야하지만 변경에 대해서는 닫혀야한다는 원칙을 위반한 것이다.

Decorater Pattern 적용

  1. DarkRoast 객체에서 시작
  2. 모카를 주문하면 DarkRoast를 Mocha로 감쌈
  3. 휘핑 크림도 같이 주문하면 추가로 Whip으로 감쌈

Beverage 추상 클래스 정의

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

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

Decorator 클래스 구현

첨가물(condiment)을 나타내는 추상 클래스(Decorator 클래스)를 구현한다.

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

음료 클래스 구현

public class Espresso extends Beverage {
    public Espresso() {
        description = "에스프레소";
    }

    public double cost() {
        return 1.99;
    }
}

public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = "하우스 블렌드 커피";
    }

    public double cost() {
        return .89;
    }
}

첨가물 클래스 구현

public class Mocha extends CondimentDecorator {
    Beverage beverage;

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

    public String getDescription() {
        return beverage.getDescription() + ", 모카";
    }

    public double cost() {
        return .20 + beverage.cost();
    }
}

Beverage 클래스를 추상클래스로 만든 이유

기존 코드에서 추상 클래스를 사용하고 있었기 때문이라고 한다. 원래 Decorator Pattern에서는 특정 추상 구성요소를 지정할 필요가 없고 인터페이스를 사용하면 된다고 한다. 하지만 기존 코드를 수정하는 것은 될 수 있으면 피하는 것이 좋기 때문에 추상 클래스를 사용해도 되는 상황이라면 그대로 추상 클래스를 사용하는 것이 나을 수도 있다고 한다.

Beverage를 인터페이스로 구현

코드 작성

public interface Beverage {  
    String description = "제목 없음";  

    String getDescription();  
    double cost();  
}

public class Espresso implements Beverage{  
    @Override  
    public String getDescription() {  
        return "에스프레소";  
    }  

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

public class HouseBlend implements Beverage{  
    @Override  
    public String getDescription() {  
        return "하우스 블랜드 커피";  
    }  

    @Override  
    public double cost() {  
        return .89;  
    }  
}

public class Mocha implements Beverage {  
    Beverage beverage;  

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

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

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

실행

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

        Beverage beverage2 = new HouseBlend();  
        beverage2 = new Mocha(beverage2);  
        beverage2 = new Mocha(beverage2);  
        System.out.println(beverage2.getDescription() + " $" + beverage2.cost());  
    }  
}

출력 결과

에스프레소 $1.99
하우스 블랜드 커피, 모카, 모카 $1.29

NOTE

데코레이터 패턴(Decorator Pattern)

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

 

 

[reference]
Head First Design Patterns

728x90