데코레이터 패턴
: 상속을 남용하는 전형적인 예를 살펴보고 객체 작성이라는 형식으로 실행중에 클래스를 꾸미는 방법을 배워봅시다. 이 패턴을 배우고 나면 원래 클래스의 코드는 전혀 바꾸지 않고도 여러분이 만든 객체에 새로운 임무를 부여할 수 있습니다.
- 커피샵의 음료를 클래스로 표현한다고 합시다.
-
Beverage는 음료를 나타내는 추상 클래스이며, 샵의 모든 음료는 이 클래스의 서브 클래스가 됩니다. 서브에서는 cost()를 새로 구현하여 각 클래스마다 가격을 다르게 측정합니다. 각 서브클래스의 생성자에서 description을 설정합니다.
-
근데.. 커피에 옵션이 추가되었습니다. 휘핑, 스팀우유, 두유, 모카,… 다양한 선택지가 있고, 중복으로 추가할 수도 있네요?
디자인원칙 : 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.
OCP(Open-Closed Principle) : 기존 코드는 건드리지 않은 채로 확장을 통해서 새로운 행동을 간단하게 추가할 수 있도록 하는 게 바로 우리의 목표입니다. 새로운 기능을 추가하는 데 있어서 매우 유연해서 급변하는 주변 환경에 잘 적응할 수 있으면서도 강하고 튼튼한 디자인을 만들 수 있겠죠.
-
- 데코레이터 패턴
- 상속을 써서 음료 가격과 첨가물(샷, 시럽, 우유 등) 가격을 합한 총 가격을 계산하는 방법은 그리 좋은 방법이 되지 못했습니다 . 클래스가 어마어마하게 많아지거나 일부 서브클래에는 적합하지 않은 기능을 베이스 클래스에 추가하게 되는 문제가 있었죠.
그래서 데코레이터 패턴은 다음과 같습니다. 특정 음료에서 시작해서, 첨가물로 그 음료를 장식”decorate”할 것입니다. 예를 들어 어떤 손님이 모카하고 휘핑 크림을 추가한 다크 로스트 커피를 주문한다면 다음과 같은 단계를 거칩니다.
- Dark Roast 객체를 가져온다.
- Mocha 객체로 장식한다.
- Whip 객체로 장식한다.
- cost() 메소드를 호출한다. 이때 첨가물의 가격을 계산하는 일은 해당 객체들에게 위임된다.
-
그러면 객체를 어떻게 ‘장식’할 수 있을까요? 힌트: 데코레이터 객체를 ‘래퍼’ 객체라고 생각해보세요.
public abstract class Beverage {
String description;
public String getDescription() {
return description;
}
public abstract int cost();
}
public abstract class CondimentDecorator extends Beverage{
@Override
public abstract String getDescription();
}
-
- 데코레이터가 적용된 예 : 자바 I/O
- BufferedInputStream과 LineNumberStream은 모두 FilterInputStream을 확장한 클래스입니다. FilterInputStream은 추상 데코레이터 클래스 역할을 하죠.
-
한번 만들어 봅시다. 입력 스트림에 있는 대문자를 전부 소문자로 바꿔주는 데코레이터를 만드는 겁니다. 즉, “I know the Decorator Pattern therefore I RULE!” 이라는 스트림을 읽어서 “i know th decorator pattern therefore i rule!”로 변환해주는 데코레이터를 만들어봅시다.
public class LowerCaseInputStream extends FilterInputStream{ public LowerCaseInputStream(InputStream in){ super(in); } public int read() throws IOException{ int c = super.read(); return (c == -1 ? c: Character.toLowerCase((char)c)); } @Override public int read(byte[] b, int off, int len) throws IOException { // TODO Auto-generated method stub int result = super.read(b, off, len); for(int i = off; i < off+result; i++){ b[i] = (byte)Character.toLowerCase((char)b[i]); } return result; } }
public class InputTest { public static void main(String[] args) { // TODO Auto-generated method stub int c; String path = InputTest.class.getResource("").getPath(); //--path = /C:/Users/2dayi/workspace/baekjoon/13460/bin/mk/pattern/decorator/last/ try{ InputStream in = new LowerCaseInputStream (new BufferedInputStream (new FileInputStream(path+"test.txt"))); while((c = in.read()) >= 0){ System.out.print((char)c); } in.close(); } catch (IOException e) { // TODO: handle exception e.printStackTrace(); } } }
I know the Decorator Pattern therefore I RULE! //test.txt
-
- 연필을 깍으며…
- 카페에서 사이즈 개념을 도입하기로 했습니다. 이제 커피는 톨, 그란데, 벤티에서 골라서 주문할 수 있습니다. Beverage 클래스에 setSize()와 getSize() 메소드를 추가했습니다. 그리고 사이즈에 따라서 첨가물 가격도 다르게 받을 계획입니다. 예를 들어, 두유 가격을 톨에서는 400원 그란데에서는 500원 벤티에서는 600원 받기로 측정했습니다. 그렇다면 데코레이션 클래스를 어떤식으로 고쳐야 할까요?