초대형 커피 전문점, 스타버즈
- 다양한 음료를 모두 포괄하는 주문 시스템을 갖추지 못했음
- 이전의 주문 시스템 클래스:
- beverage라는 음료를 나타내는 추상클래스가 있으며 매장에서 판매되는 모든 음료는 이 클래스의 서브클래스가 된다.
- cost() 메소드는 추상 메소드로 서브클래스에서 이 메소드를 구현해서 새로 정의해야함
- description 이라는 인스턴스 변수는 각 서브클래스에서 설정되며, 여기에는 가장 훌륭한 다크 로스트 커피 같은 음료 설명이 저장됨, description 변수에 저장된 내용은 getDescription() 메소드를 호출해서 확인할 수 있음
- HouseBlend, DarkRoast Decaf Espresso 라는 서브클래스에서는 음료의 가격을 리턴하는 cost() 메소드를 구현해야함
- 커피 주문할 때 우유나 두유, 모카 추가, 휘핑크림 추가와 같은 엑스트라 오더를 구현하기 위해 복잡한 클래스가 만들어짐
- 해결책 1: 인스턴스 변수와 슈퍼클래스 상속을 써서 첨가물을 관리
- milk, soy, mocha, whip 불리언 변수 추가
- hasMilk, setMilk 등등 게터와 세터 메소드로 불리언 값을 알아내거나 설정
- 슈퍼클래스에 있는 cost()에서 첨가물의 가격을 계산하고 서브클래스에서 cost 메소드를 오버라이드할 때 그 기능을 확장하여 특정 음료의 가격을 더 하는 역할
OCP 살펴보기
OCP란 : open-closed principle → 클래스는 확장에는 열려 있지만 변경에는 닫혀있어야한다.
우리의 목표는 기존 코드를 건드리지 않고 확장으로 새로운 행동을 추가하는 것 → 새로운 기능을 추가할 때 급변하는 주변 환경에 잘 적응하고 유연하고 튼튼한 디자인을 만들 수 있음
모든 부분에서 OCP를 준수하는 것은 불가능함, OCP를 준수하는 객체지향 디자인을 만들려면 적지 않은 시간과 노력이 필요함. 디자인의 모든 부분을 깔끔하게 정돈할 만큼 여유가 있는 상황도 흔치 않고, 그렇게 할 필요도 없음. OCP를 지키기 위해서 새로운 단계의 추상화가 필요한 경우가 종종 있는데 추상화를 하다 보면 코드가 복잡해짐. 그래서 우리가 디자인한 것 중에서 가장 바뀔 가능성이 높은 부분을 중점적으로 살펴보고 OCP를 적용하는 방법이 가장 좋음
데코레이터 패턴 살펴보기
- 첨가물로 음료를 장식하는 단계
- DarkRoast 객체를 가져온다
- Mocha 객체로 장식한다.
- Whip 객체로 장식한다.
- cost() 메소드를 호출한다. 이때 첨가물의 가격을 계산하는 일은 해당 객체에게 위임한다.
주문 시스템에 데코레이터 패턴 적용하기
- 우선 가장 바깥쪽에 있는 데코레이터인 whip의 cost 메소드를 호출
- whip은 mocha의 cost 메소드를 호출
- mocha는 다시 darkroast와 cost를 호출
- darkroast는 99센트(가격)을 리턴
- mocha는 darkroast로부터 리턴받은 가격에 모카 값 20센트를 더해 1.19달러 리턴