따봉도치야 고마워

Head First Design Patterns : (3)데코레이터 패턴 본문

프로그래밍/공부

Head First Design Patterns : (3)데코레이터 패턴

따봉도치 2020. 9. 11. 16:42

데코레이터 패턴

  • 객체에 추가적인 요건을 동적으로 첨가한다.
  • 데코레이터는 서브클래스를 만드는 것을 통해 기능을 유연하게 확장하는 방법을 제공한다.
  • 데코레이터의 수퍼클래스 == 자신이 장식하고 있는 객체의 수퍼클래스
  • -> 원래 객체가 들어갈 자리에 데코레이터 객체를 넣어도 상관 없음
  • 한 객체를 여러개의 데코레이터로 감쌀 수 있음
  • 데코레이터는 자신이 장식하는 객체에게 어떤 행동을 위임하는 것 외에 원하는 추가 작업을 수행 가능
  • 객체는 언제든 감쌀 수 있기 때문에 실행중에 마음대로 적용 가능

장단점?

  • 장점 : 하나의 객체에 추가해야될 기능들이 다양하고 일정하지 않을 때 효율적
  • 단점 : 데코레이터 클래스가 너무 많아져 코드가 복잡해질 수도

 

스타버즈 예제

- 상속을 사용하면 서브 클래스가 매우 많아지거나, 일부 서브클래스에 적합하지 않은 기능을 베이스클래스에 추가하게 됨

- 데코레이터 패턴을 사용해 객체를 장식하자

- Beverage : 추상 구성요소, HouseBlend : 구상 구성요소, CondimentDecorator : 추상 데코레이터, Mocha : 구상 데코레이터

 

  1. ' 모카, 휘핑크림을 추가한 다크 로스트 커피를 주문한다면 '
  2. DarkRoast 객체 (Beverage상속) 생성
  3. Mocha 객체 생성해 DarkRoast 감싸기 : Mocha는 데코레이터, 해당 객체의 형식은 장식하고 있는 객체를 반영함
  4. Whip 객체 생성해 Mocha를 감쌈
  • 가격 계산 시 가장 바깥 쪽 데코레이터 > 장식하고 있는 객체 순으로 계산 위임
  • 가장 안쪽 객체에 도달 시 값을 리턴하고, 리턴 받은 값을 차례대로 사용해 최종 결과 리턴
package headfirst.decorator.starbuzz;

public class StarbuzzCoffee {
 
	public static void main(String args[]) {
		Beverage beverage = new Espresso();
		System.out.println(beverage.getDescription() 
				+ " $" + beverage.cost());
 
		Beverage beverage2 = new DarkRoast();
		beverage2 = new Mocha(beverage2);
		beverage2 = new Mocha(beverage2);
		beverage2 = new Whip(beverage2);
		System.out.println(beverage2.getDescription() 
				+ " $" + beverage2.cost());
 
		Beverage beverage3 = new HouseBlend();
		beverage3 = new Soy(beverage3);
		beverage3 = new Mocha(beverage3);
		beverage3 = new Whip(beverage3);
		System.out.println(beverage3.getDescription() 
				+ " $" + beverage3.cost());
	}
}

 

Q &A

1. 특정 구성요소인지 확인한 다음 처리해야 하는 작업의 경우 이 코드를 그대로 쓸 수 없지 않나

(하우스 블렌드 할인 작업의 경우, 하우스 블렌드인지 알 수 없으니)

맞음. 이런 작업이 필요하다면, 데코레이터 패턴을 적용해야하는지에 대해 다시 한 번 고려해야함

 

2. 데코레이터를 빼먹는 실수할 우려

실수 할 가능성이 있음. 하지만 일반적으로 팩ㅇ토리나 빌더 같은 다른 패턴을 써서 사용하게 됨. 그럼 이런 걱정은 하지 않아도 됨.

 

 

 


 

데코레이터가 적용된 예 : 자바 I/O

java.io 패키지의 많은 부분이 데코레이터 패턴을 바탕으로 만들어져있음

 

  • FileInputStream : 데이터를 읽어들일 수 있게 해주는 기본 구성요소
  • BufferedInputStream : 구상 데코레이터 - 속도 향상 위해 버퍼에 내용 저장, 한 번에 한줄 씩 읽기 위한 readLine() 구현
  • LineNumberInputStream : 구상 데코레이터 - 데이터를 읽을 때 행번호를 붙여주는 기능 추가

 

데코레이터 패턴에 적용된 디자인 원칙

  • OCP(Open-Closed Priciple) : 클래스는 확장에 대해서는 열려있어야 하지만, 코드 변경에 대해선 닫혀 있어야 함
    • 기존 코드를 건들이지 않은 채 새로운 행동을 추가
    • 모든 경우에 OCP를 적용하기란 쉽지않음, 가장 바뀔 확률이 높은 부분을 잘 살펴보고 적용. 남용x

 

 

 


 

연필을 깎으며

page 121

1.

public float cost(){
	float cost = 0.0;
    if(hasMilk()){
    	cost += milkCost;
    }
    if(hasSoy()){
    	cost += SoyCost;
    }
    if(hasMocha()){
    	cost += MochaCost;
    }
    if(hasWhip()){
    	cost += WhipCost;
    }
    
    return cost;
}

2.

public float cost(){
	return darkRoastCost + super.cost();
}

 

 

page 122

실행 중에 첨가물 변경 불가

첨가물 별 양에 따라 금액이 달라질 수 있음

 

page 137

cost() 메소드에서 beverage의 size를 가져와 사이즈에 맞게 더하도록 추가

 

Comments