데코레이터 패턴 정의
위의 정의를 보면 데코레이터 패턴의 역할은 알 수 있지만, 실제 코드를 구현할 때 어떤식으로 적용해야 할지는 알기 힘듭니다. 아래의 클래스 다이어그램을 한번 살펴봅시다.
커피숍 데코레이터 패턴으로 만들어보기
1. Beverage
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
2. CondimentDecorator
public abstract class CondimentDecorator extends Beverage {
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
}
3. HouseBlend
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "HouseBlend";
}
public double cost() {
return .89;
}
}
4. DarkRoast
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "DarkRoast";
}
public double cost() {
return 0.99;
}
}
5. Espresso
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
public double cost() {
return 1.99;
}
}
6. Decaf
public class Decaf extends Beverage {
public Decaf() {
description = "Decaf";
}
public double cost() {
return 1.05;
}
}
7. Milk
public class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
public double cost() {
return .10 + beverage.cost();
}
}
8. Mocha
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ",Mocha";
}
public double cost() {
return .10 + beverage.cost();
}
}
9. Whip
public class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ",Whip";
}
public double cost() {
return .10 + beverage.cost();
}
}
10. Soy
public class Soy extends CondimentDecorator {
public Soy(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
public double cost() {
return .15 + beverage.cost();
}
}
추가해보기 Tea, Water, Vanilla, Caramel
11. Tea
public class Tea extends Beverage{
public Tea() {
description = "Tea";
}
public double cost() {
return 0.88;
}
}
12. Water
public class Water extends CondimentDecorator{
public Water(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Water";
}
public double cost() {
return .01 + beverage.cost();
}
}
13. Vanilla
public class Vanilla extends CondimentDecorator{
public Vanilla(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Vanilla";
}
public double cost() {
return .21 + beverage.cost();
}
}
14.Caramel
public class Caramel extends CondimentDecorator{
public Caramel(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Caramel";
}
public double cost() {
return .15 + beverage.cost();
}
}
추가해보기 Tall, Grande, Venti
15. Tall
public class Tall extends CondimentDecorator {
public Tall(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Tall";
}
public double cost() {
return beverage.cost();
}
}
16. Grande
public class Grande extends CondimentDecorator {
public Grande(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Grande";
}
public double cost() {
return beverage.cost() * 2;
}
}
17. Venti
public class Venti extends CondimentDecorator{
public Venti(Beverage beverage) {
super(beverage);
}
public String getDescription() {
return beverage.getDescription() + ", Venti";
}
public double cost() {
return beverage.cost() * 3;
}
}
왜 이건 안될까? (Mocha 수정하기)
한번 모카를 이렇게 수정해보겠습니다. Tea가 문자열에 포함되어 있으면, 즉 Tea를 주문하고 Mocha를 첨가하려고 하면 추가할수 없다고 알려주는 코드입니다. 이렇게 코드를 쓰면 사실 잘 되긴 합니다만 문제가 있습니다. 데코레이터 는 그것이 감싸고있는 구성요소들중에 무엇이 구체적으로 있는지 모릅니다. 즉, Mocha는 자신이 꾸미고 있는, 혹은 감싸고 있는객체가 뭔지 모른다!는것이 학계의 점심입니다. 지금은 가능할지 몰라도 조건을 걸고 구성요소에 따라서 다른행동을 하는 구조를 만드는것은 데코레이터를 사용할 이유가 없습니다.
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
super(beverage);
}
public String getDescription() {
if(beverage.getDescription().contains("Tea")) {
System.out.println("차에는 Mocha를 추가할 수 없습니다.");
return beverage.getDescription();
}
return beverage.getDescription() + ", Mocha";
}
public double cost() {
if(beverage.getDescription().contains("Tea")) {
return beverage.cost();
}
return .20 + beverage.cost();
}
}
데코레이터 패턴 정리
[1] 데코레이터의 수퍼클래스는 자신이 장식하고 있는 객체의 수퍼클래스와 같다.
[2] 한 객체를 여러개의 데코레이터로 감쌀 수 있다.
[3] 데코레이터는 자신이 감싸고 있는 객체와 같은 수퍼클래스를 가지고 있기 때문에 원래 객체(싸여져 있는 객체)가 들어갈 자리에 데코레이터 객체를 집어 넣어도 상관없다.
[4] 데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 것 외에 원하는 추가적인 작업을 수행할 수 있다.
[5] 객체는 언제든지 감쌀 수 있기 때문에 실행 중에 필요한 데코레이터를 마음대로 적용할 수 있다.
[6] 데코레이터는 그것을 감싸는 구성요소들 중에 무엇이 구체적으로 있는지 알 수 없다. 따라서, 그것이 감싸는 특정한 구성요소에 따라서 다른 행동을 할 수는 없다. (예를 들면, "DarkRoast 커피에 추가된 휘핑크림은 50%할인"과 같은 행동은 데코레이터에서는 구조적으로 힘들다.
데코레이터가 적용된 예 : 자바I/O
대표사진 삭제
사진 설명을 입력하세요.
새로운 Java I/O 데코레이터 추가(LowerCaseInputStream)
대표사진 삭제
사진 설명을 입력하세요.
입력 스트림에 있는 대문자를 전부 소문자로 바꿔주는 데코레이터를 만들어봅니다. 먼저 java.io를 임포트해주고요, 모든 InputStream의 추상 데코레이터인 FilterInputStream을 확장합니다. 두개의 read()메소드를 구현합니다. 각각byte값 하나, 또는 byte배열을 읽고 각 byte(문자를 나타냄)를 검사하여 대문자이면 소문자를 반환합니다.
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = in.read();
return (c == -1 ? c : Character.toLowerCase((char) c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = in.read(b, offset, len);
for (int i = offset; i < offset + result; i++) {
b[i] = (byte) Character.toLowerCase((char) b[i]);
}
return result;
}
}
아래는 실험을 위한 메인함수입니다.
public class InputTest {
public static void main(String[] args) throws IOException {
int c;
try {
InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("test.txt")));
while ((c = in.read()) >= 0) {
System.out.print((char) c);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
핵심 정리
[1] 상속을 통해 확장을 할 수도 있지만, 디자인의 유연성 면에서 보면 별로 좋지 않음
[2] 구성과 위임을 통해서 실행 중에 새로운 행동을 추가할 수 있음
[3] 데코레이터 패턴에서는 구상 구성요소(Concrete Component)를 감싸주는 데코레이터들을 사용
[4] 데코레이터 클ㄹ래스의 형식은 그 클래스가 감싸고 있는 클래스의 형식을 반영
[5] 데코레이터에서는 자기가 감싸고 있는 구성요소의 메소드를 호출한 결과에 새로운 기능을 더함으로써 행동을 확장
[6] 구성요소를 감싸는 데코레이터의 개수에는 제한 없음
[7] 구성요소의 클라이언트에서 구성요소의 구체적인 형식에 의존하게 되는 경우는 데코레이터 패턴을 사용할 수 없음
[8] 데코레이터 패턴을 사용하면 자잘한 객체들이 매우 많이 추가될 수 있고, 데코레이터를 너무 많이 사용하면 코드가 필요 이상으로 복잡해질 수 있음