상세 컨텐츠

본문 제목

[Design Pattern] Decorator Pattern

JAVA

by jeonghojin 2023. 3. 24. 00:45

본문

Decorator Pattern

 
객체의 결합을 통해 기능을 동적으로 유연하게 확장할 수 있게 해주는 패턴
즉, 기본 기능에 추가할 수 있는 기능의 종류가 많은 경우에 각 추가 기능을 Decorator 클래스로 정의한 후, Decorator 객체를 조합함으로써 추가 기능의 조합을 설계하는 방식
 

기본 기능에 추가할 수 있는 많은 종류의 부가 기능에서 파생되는 다양한 조합을 동적으로 구현할 수 있게 하는 패턴이다.

Component :
기본 기능을 뜻하는 ConcreteComonent와 추가 기능을 뜻하는 Decorator의 공통 기능을 정의
ConcreteComponent :
기본 기능을 구현하는 클래스
Decorator :
많은 수가 존재하는 구체적인 Decorator의 공통 기능을 제공
ConcreteDecoratorA, ConcreteDecoratorB :
Decorator의 하위 클래스로 기본 기능에 추가되는 개별적인 기능을 뜻함.
ConcreteDecorator 클래스는 ConcreteComponent 객체에 대한 참조가 필요한데, 이는 Decorator 클래스에서 Component클래스로의 '합성(composition)의 관계'를 통해 표현

 

구조 패턴 :
- 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴
- 예를 들어, 서로 다른 인터페이스를 지닌 2개의 객체를 묶어 단일 인터페이스를 제공하거나 객체들을 서로 묶어 새로운 기능을 제공하는 패턴이다.
합성 관계 :
- 생성자에서 필드에 대한 객체를 생성하는 경우
- 전체 객체의 라이프 타임과 부분 객체의 라이프 타임은 의존적이다.
- 즉, 전체 객체가 없어지면 부분 객체도 없어진다.

데코레이터 패턴 사용하는 경우
- 클래스의 요소들을 계속해서 수정하며 사용하는 구조가 필요한 경우
- 여러 요소들을 조합해서 사용하는 클래스 구조인 경우

 
예를 들어 커피를 주문하는 코드를 작성한다.
 
abstract Beverage

public abstract class Beverage {

    private String description;
    private double cost;

    public void setDescription(String description) {
        this.description = description;
    }
    public String getDescription() {
        return this.description;
    }

    public void setCost(double cost) {
        this.cost = cost;
    }

    public double getCost() {
        return this.cost;
    }
}
public class Espresso extends Beverage {

    public Espresso() {
        setDescription("espresso");
        setCost(1.00);
    }
}

 

public class MilkEspresso extends Beverage{
    public MilkEspresso(Beverage beverage) {

        setDescription(beverage.getDescription().concat(", Milk"));
        setCost(beverage.getCost()+0.20);
    }
}
public class WhipEspresso extends Beverage{
    public WhipEspresso(Beverage beverage) {
        setDescription(beverage.getDescription().concat(", Whip"));
        setCost(beverage.getCost()+0.10);
    }
}
public class MilkWhipEspresso extends Beverage {
    public MilkWhipEspresso(Beverage beverage) {
        setDescription(beverage.getDescription().concat(", Milk"));
        setDescription(getDescription().concat(", Whip"));
        setCost(beverage.getCost()+0.20);
        setCost(getCost()+0.10);
    }

}

 
Main

public class Main {
    public static void main(String[] args) {
        Beverage espresso = new Espresso();
        System.out.println("description : "+espresso.getDescription() + " / cost : "+espresso.getCost());

        Beverage milkEspresso = new MilkEspresso(espresso);
        System.out.println("description : "+milkEspresso.getDescription() + " / cost : "+milkEspresso.getCost());

        Beverage whipEspresso = new WhipEspresso(espresso);
        System.out.println("description : "+whipEspresso.getDescription() + " / cost : "+whipEspresso.getCost());

        Beverage milkWhipEspresso = new MilkWhipEspresso(espresso);
        System.out.println("description : "+milkWhipEspresso.getDescription() + " / cost : "+milkWhipEspresso.getCost());
    }
}

 
이와 같이 상속을 통해 조합의 각 경우를 설계한다면 각 조합별로 하위 클래스는 계속 구현되어야 할 것이다.
즉, 다양한 기능의 조합을 고려해야 하는 경우 상속을 통한 기능의 확장은 각 기능별로 클래스를 추가해야 한다는 단점이 있다.
 
메뉴의 가지 수가 많아질수록 문제는 더욱더 심각해질 것이다.

경우기본추가클래스 명
MilkWhip
1O  Espresso
2OO MilkEspresso
3O OWhipEspresso
4OOOMilkWhipEspresso
...............

 


abstract Beverage

public abstract class Beverage {

    private String description;


    protected void setDescription(String description) {
        this.description = description;
    }

    public String getDescription() {
        return this.description;
    }

    protected abstract double getCost();
}
public class Espresso extends Beverage {

    public Espresso() {
        setDescription("espresso");
    }

    @Override
    protected double getCost() {
        return 1.99;
    }
}

 
abstract BeverageDecorator

public abstract class BeverageDecorator extends Beverage{

    private Beverage beverage;
    private double cost = 0.0;

    protected void setCost(double cost) {
        this.cost = cost;
    }

    protected void setBeverage(Beverage beverage) {
        this.beverage = beverage;
    }

    public Beverage getBeverage() {
        return this.beverage;
    }

    protected void setDecorationCost(double cost) {
        this.cost = cost;
    }

    protected double getDecorationCost() {
        return this.cost;
    }
}
public class Milk extends BeverageDecorator{
    public Milk(Beverage beverage) {
        setBeverage(beverage);
        setDecorationCost(0.20);
    }

    @Override
    public String getDescription() {
        return getBeverage().getDescription().concat(", Milk");
    }

    @Override
    protected double getCost() {
        return getBeverage().getCost() + getDecorationCost();
    }
}
public class Whip extends BeverageDecorator{

    public Whip(Beverage beverage) {
        setBeverage(beverage);
        setDecorationCost(0.10);
    }

    @Override
    public String getDescription() {
        return getBeverage().getDescription().concat(", Whip");
    }

    @Override
    protected double getCost() {
        return getBeverage().getCost() + getDecorationCost();
    }
}

 
Main

public class Main {
    public static void main(String[] args) {
        // 기본 에스프레소
        Beverage beverage = new Espresso();
        String description = beverage.getDescription();
        double cost = beverage.getCost();
        System.out.println(description + " / cost "+cost);
        
        // 우유 추가
        beverage = new Milk(beverage);
        description = beverage.getDescription();
        cost = beverage.getCost();
        System.out.println(description + " / cost "+cost);

        // 휘핑 추가
        beverage = new Whip(beverage);
        description = beverage.getDescription();
        cost = beverage.getCost();
        System.out.println(description + " / cost "+cost);
    }
}

Decorator Pattern 장/단점

장점 :
- 기존 코드를 수정하지 않고 확장할 수 있다.(OCP 원칙 만족)
- 구성과 위임을 통해서 실행중에 새로운 행동을 추가할 수 있다.
 
단점 :
- 특정 구성요소인지 확인한 다음 어떤 작업을 처리하는 경우에 데코레이터 패턴을 사용하기 어려울 수 있다.
ex : 특정 커피는 할인이 안되는 경우.
- 클래스가 많이 추가되는 경우 복잡해질 수 있다.
 
 


참고 :

더보기

'JAVA' 카테고리의 다른 글

[Design Pattern] State Pattern  (0) 2023.03.26
[Design Pattern] Builder Pattern  (0) 2023.03.25
[Design Pattern] Strategy Pattern  (0) 2023.03.23
[Design Pattern] Abstract Factory Pattern  (0) 2023.03.22
[Design Pattern] Command Pattern  (0) 2023.03.21

관련글 더보기