본문 바로가기
스터디/헤드퍼스트 디자인패턴

[헤드퍼스트 디자인패턴] 03. 데코레이터 패턴(Decorator Pattern)

by 디토20 2022. 6. 18.
반응형

 

 

 

[헤드퍼스트 디자인패턴] 03. 데코레이터 패턴(Decorator Pattern)

 

1. 데코레이터 패턴이란?

 객체에 추가 요소를 동적으로 더할 수 있다. 데코레이터를 사용하면 서브클래스를 만들 때보다 훨씬 유연하게 기능을 확장할 수 있다.

 

 

2. 구현 목표

1. 카페의 주문 시스템을 만든다.

2. 고객은 커피를 주문할 때 우유나 두유, 모카를 추가하고 그 위에 휘핑크림을 얹기도 한다.

 

 

3. 고려사항

 - 추후 옵션이 늘어날 수 있다.

 - 각각을 추가할 때마다 커피 가격이 올라가는 점을 고려해야 한다.

 

 

4. 구현

4.1 문제가 있는 설계 (상속을 사용 할 경우)

문제점
  • 첨가물이 가격이 바뀔 때 마다 기존 코드를 수정해야 한다.
  • 첨가물 종류가 많아지면 새로운 메소드를 추가해야 하고, cost() 메소드를 수정해야 한다.
  • 새로운 음료가 출시 될 경우, 기존의 첨가물이 들어가면 안되는 경우가 있다. ex) 아이스티가 hasWhip()을 상속받게 됨
  • 고객이 더블 모카를 주문할 경우???

 

 

 

4.2 데코레이터 패턴을 활용한 구현

 

  1. DarkRoast 객체에서 시작한다.

 

 


  2. 손님이 Mocha를 주문했으니 Mocha객체를 만들고 그 객체로 DarkRoast를 감싼다.



  3. 손님이 휘핑크림도 같이 주문했으므로  Whip 데코레이터를 만들어 Mocha를 감싼다.

 
 
  4. 가장 바깥쪽에 있는 데코레이터 Whip의 cost()를 호출한다.

 

  • Whip에서는 Mocha의 cost() 메소드 호출 
  • Mocha에서는 DarkRoast의 cost() 호출
  • DarkRoast에서는 가격과 이름을 반환
  • Mocha에서는 DarkRoast의 리턴값과 모카값을 더해 반환
  • Whip에서는 Mocha에서 받은 가격에 Whip가격을 더해 최종 가격을 반환
     

 

 

-> 데코레이터와 데코레이터로 감싸는 객체의 형식을 맞추기 위해 Beaverage를 상속 받아 사용한다

 

 

 

 

4.2.1 코드 구현

public abstract class Beverage {
    String description = "제목 없음";
    
    public String getDescription {
    	return description;
    }
    
    public abstract double Cost();
}


public abstract class CondimentDecorator : Beverage {
    private Beverage beverage;
    public abstract String getDescription();
}


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

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


public class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }
    
    public String getDescription() {
    	return beverage.getDescription() + "모카";
    }
    
    @Override
    public double cost() {
        return beverage.cost() + .20;
    }
}

public class Whip extends CondimentDecorator {
    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }
    
    public String getDescription() {
    	return beverage.getDescription() + "휘핑";
    }
    
    @Override
    public double cost() {
        return beverage.cost() + .10;
    }
}

public class DecoratorPattern {
       public static void main(String[] args) {
            Beverage beverage = new Espresso();

            beverage = new Mocha(beverage);
            beverage = new Mocha(beverage);
            beverage = new Whip(beverage);
            System.out.println(beverage.getDescription() + "$" + beverage.cost())
        }
}

 

-> 상속을 이용해 서브클래스를 만들 경우, 그 행동은 컴파일할 때 완전히 결정되고, 모든 서브클래스에서 똑같은 행동을 상속받아야 한다. 그러나 구성으로 객체의 행동을 확장하면 기존 코드를 수정하지 않고 새로운 코드를 만들어 실행 중에 동적으로 행동을 설정 할 수 있다. 기존 코드를 건드리지 않으므로 코드 수정에 따른 버그나 의도하지 않은 부작용을 원천봉쇄할 수 있음.

 

 

 

디자인 원칙

클래스는 확장에 열려있어야 하지만
변경에는 닫혀있어야 한다.

 

 

 

5. 데코레이터 패턴의 특징

  • 데코레이터 클래스의 형식은 그 클래스가 감싸는 클래스 형식을 반영한다.
  • 데코레이터는 자기가 감싸고 있는 구성 요소의 메소드를 호출한 결과에 새로운 기능음 더함으로써 행동을 확장한다.
  • 구성 요소를 감싸는 데코레이터의 개수에는 제한이 없다.
  • 구성 요소의 클라이언트는 데코레이터의 존재를 알 수 없다.
  • 데코레이터 패턴을 상요하면 자잘한 객체가 매우 많이 추가될 수 있고, 데코레이터를 너무 많이 사용하면 코드가 필요 이상으로 복잡해 진다. -> 팩토리와 빌더 패턴으로 보완 가능

 

728x90
반응형

댓글