헤드퍼스트 디자인 패턴 3장 | 객체 꾸미기 - 데코레이터 패턴
(에릭 프리먼 외 4인, 서환수 옮김, 한빛미디어)
데코레이터 패턴을 배우면 기존 클래스 코드를 바꾸지 않고도 객체에 새로운 임무를 추가할 수 있습니다.
- 113p
스승 : 상속에 관해서 깊이 생각해 보았나요?
제자 : 예, 스승님. 상속이 강력하긴 하나, 상속을 사용한다고 해서 무조건 유연하거나 관리하기 쉬운 디자인이 만들어지지는 않는다는 사실을 깨달았습니다.
스승 : 그래요... 뭔가 깨달음을 얻었군요. 그러면 상속 말고 어떤 걸로 재사용이라는 목표를 달성할 수 있을까요?
제자 : 구성과 위임으로 실행 중에 행동을 '상속'하는 방법이 있다는 사실을 배웠습니다. ... 서브클래스를 만드는 방식으로 행동을 상속받으면 그 행동은 컴파일할 때 완전히 결정됩니다. 게다가 모든 서브클래스에서 똑같은 행동을 상속받아야 합니다. 하지만 구성으로 객체의 행동을 확장하면 실행 중에 동적으로 행동을 설정할 수 있습니다.
스승 : 훌륭합니다. 드디어 구성의 위력에 눈을 떴군요!
제자 : 예, 이 기술을 활용하면 객체에 여러 임무를 새로 추가할 수 있습니다. 심지어 슈퍼클래스를 디자인했던 사람이 전혀 생각하지 못했던 내용을 추가할 수도 있습니다. 클래스 코드를 전혀 건드리지 않고도 말입니다.
스승 : 코드 관리에 구성이 어떤 영향을 미치는지도 알고 있나요?
제자 : 예, 방금 그 내용을 말씀드리려고 했습니다. 객체를 동적으로 구성하면 기존 코드를 고치는 대신 새로운 코드를 만들어서 기능을 추가할 수 있습니다. 기존 코드는 건드리지 않으므로 코드 수정에 따른 버그나 의도하지 않은 부작용을 원천봉쇄할 수 있습니다.
스승 : ... 코드는 밤의 연꽃처럼 변경에는 닫혀 있고 아침의 연꽃처럼 확장에는 활짝 열려 있어야 한다는 사실을요.
- 119p
디자인 원칙 : 클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다. (OCP-Open-Closed Principle)
- 120p
상속을 써서 음료 가격과 첨가물(샷, 시럽, 우유, 휘핑크림 등) 가격을 합해서 총 가격을 산출하는 방법은 그리 좋은 방법이 아니었습니다. 클래스가 어마어마하게 많아지거나 일부 서브클래스에는 적합하지 않은 기능을 추가해야 하는 문제가 있었죠.
다른 방법을 한번 생각해봅시다. 일단 특정 음료에서 시작해서 첨가물로 그 음료를 장식(decorate)해 볼까요? 예를 들어 어떤 고객이 모카와 휘핑크림을 추가한 다크 로스트 커피를 주문한다면 다음과 같이 장식할 수 있습니다.
1. DarkRoast 객체를 가져온다.
2. Mocha 객체로 장식한다.
3. Whip 객체로 장식한다.
4. cost() 메소드를 호출한다. 이때 첨가물의 가격을 계산하는 일은 해당 객체에게 위임한다.
- 122p
- 124p
커피 주문 시스템 코드 만들기
Beverage 클래스
public abstract class Beverage { => Beverage는 추상 클래스이며 getDescription()과 cost()라는 2개의 메소드를 가집니다.
String description = "제목 없음";
public String getDescription() {
return description;
}
=> getDescript은 이미 구현, cost()는 서브클래스에서 구현
public abstract double coust();
}
첨가물(condiment)을 나타내는 추상클래스(데코레이터 클래스)를 구현
public abstract class CondimentDecorator extends Beverage { =>Beverage 객체가 들어가야 하므로 Beverage 클래스를 확장
Beverage beverage; => 각 데코레이터가 감쌀 음료를 나타내는 Beverage 객체를 여기서 지정. 어떤 음료든 감쌀 수 있도록 Beverage 슈퍼 클래스 유형을 사용
public abstract String getDescription();
=> 모든 첨가물 데코레이터에 getDescription() 메소드를 새로 구현하도록 만들 계획. 그래서 추상 메소드로 선언.
}
- 129p
음료 코드 구현
public class Espresso extends Beverage {
public Espresso() {
description = "에스프레소";
} => descprtion이라는 변수는 Beverage로부터 상속
public double cost() {
return 1.99;
}
=> 첨가물 가격을 걱정할 필요 없이 그냥 에스프레소 가격인 1.99 달러를 리턴
}
- 130p
첨가물 코드 구현하기
public class Mocha extends CondimentDecorator { => Mocha는 데코레이터라서 Condiment를 확장 (Condiment에서 Beverage를 확장)
public Mocha(Beverage beverage) {
this.beverage = beverage;
} => Mocha 인스턴스에는 Beverage의 레퍼런스가 들어있습니다.
=> 1) 감싸고자 하는 음료를 저장하는 인스턴스 변수
=> 2) 인스턴스 변수를 감싸고자 하는 객체로 설정하는 생성자
public String getDescription() {
return beverage.getDescription() + ", 모카";
}
=> 설명에 음료 이름(다크 로스트)만 들어있으면 안 되겠죠. 첨가되는 각 아이템도 설명에 추가합시다.
=> 예를 들면 '다크 로스트, 모카' 같은 식으로 말이죠.
public double coust() {
return beverage.cost() + .20'
}
=> 음료 가격에 모카를 추가한 가격을 계산
=> 우선 장식하고 있는 객체에 가격을 구하는 작업을 위임해서 음료값을 구한 다음, 거기에 모카 가격을 더하고, 그 합을 리턴
}
- 131p
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); => Mocha로 감쌈
beverage2 = new Mocha(beverage2); => 모카샷 하나 더 추가
beverage2 = new Whip(beverage2); => whip으로 감싸기
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());
}
}
- 132p
데코레이터가 적용된 예: 자바I/0
- 134p
'Book' 카테고리의 다른 글
[독서 기록] 헤드퍼스트 디자인 패턴 5장 | 싱글턴 패턴 - 하나뿐인 특별한 객체 만들기 (0) | 2022.11.15 |
---|---|
[독서 기록] 헤드퍼스트 디자인 패턴 4장 | 팩토리 패턴 - 객체지향 빵 굽기 (0) | 2022.11.14 |
[독서 기록] 헤드퍼스트 디자인 패턴 2장 | 객체들에게 연락 돌리기 - 옵저버 패턴 (0) | 2022.11.14 |
[독서 기록] 헤드퍼스트 디자인 패턴 1장 | 디자인 패턴 소개와 전략 패턴 (에릭 프리먼, 엘리자베스 롭슨, 케이시 시에라, 버트 베이츠 지음, 서환수 옮김, 한빛미디어) (1) | 2022.11.14 |
[독서 기록] 클린 코드 11장, 12장 , 13장 외 / 시스템, 창발성, 동시성 (1) | 2022.11.13 |