데코레이터 패턴(Decorator Pattern)

디자인패턴 2014. 11. 11. 14:19

1. 데코레이터 패턴이란?


 데코레이터 패턴에서는 객체에 추가 요소를 구성(Composition)과 위임(Delegate)를 통해 동적으로 더할 수 있다. Concrete Component를 감싸는 데코레이터(Decorator)를 사용하면 Subclass를 만드는 경우에 비해 훨씬 유연하게 기능을 확장할 수 있다.



2. 데코레이터 패턴 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
public abstract class Beverage {
 String description = "";
  
 public String getDescription() {
  return description;
 }
  
 // SubClass에서 구현해야 함.
 public abstract double cost();
}
 
 
// 음료를 구현하기 위해 Beverage 클래스를 상속한다.
public class DarkRoast extends Beverage{
 public DarkRoast() {
  description = "DarkRoast Coffee";
 }
  
 @Override
 public double cost() {
  // 음료 클래스에서는 첨가물 가격을 걱정할 필요가 없다.
  // 그냥 DarkRoast의 가격을 리턴하면 된다.
  return .99;
 }
 
}
 
 
public class Decaf extends Beverage {
 public Decaf() {
  description = "Decaf Coffee";
 }
  
 @Override
 public double cost() {
  return 1.05;
 }
 
}
 
 
public class Espresso extends Beverage{
 
 public Espresso() {
  description = "Espresso";
 }
  
 @Override
 public double cost() {
  return 1.99;
 }
 
}
 
 
public class HouseBlend extends Beverage{
 public HouseBlend() {
  description = "HouseBlend Coffee";
 }
 
 @Override
 public double cost() {
  return .89;
 }
}
 
 
// 커피의 첨가물(Condiment)을 나타내는 Abstract 클래스(Decorator 클래스)
// 데코레이터 클래스의 형식은 그 클래스가 감싸고 있는 클래스의 형식을 반영한다.
// 그러므로, Beverage 객체가 들어갈 자리에 들어갈 수 있어야 하므로 Beverage 클래스를 상속한다.
public abstract class CondimentDecorator extends Beverage {
 public abstract String getDescription();
}
 
 
// Mocha는 Decorator이기 때문에 CondimentDecorator를 상속한다.
public class Mocha extends CondimentDecorator {
 // Wrapping 하고자 하는 음료를 저장하기 위한 Instance 변수
 Beverage beverage;
  
 public Mocha(Beverage beverage) {
  // Instance 변수를 감싸고자 하는 객체를 설정하기 위한 생성자.
  this.beverage = beverage;
 }
 
// 데코레이터에서는 자기가 감싸고 있는 Component의 메소드를 호출한 결과에 새로운 기능을 더함으로써 행동을 확장한다.
 @Override
 public String getDescription() {
  // Decorate 하고 있는 객체에 작업을 Delegate한 다음, 그 결과에 ", Mocha"를 추가한 결과를 return한다.
  return beverage.getDescription() + ", Mocha";
 }
 
 @Override
 public double cost() {
  // Decorate 하고 있는 객체에 가격을 구하는 작업을 Delegate해서 음료 자체의 값을 구하고, Mocha 가격을 더해서 합을 return한다.
  return .20 + beverage.cost();
 }
  
}
 
 
public class Soy extends CondimentDecorator {
 Beverage beverage;
  
 public Soy(Beverage beverage) {
  this.beverage = beverage;
 }
  
 @Override
 public String getDescription() {
  return beverage.getDescription() + ", Soy";
 }
 
 @Override
 public double cost() {
  return beverage.cost() + .15;
 }
 
}
 
 
public class SteamMilk extends CondimentDecorator {
 Beverage beverage;
  
 public SteamMilk(Beverage beverage) {
  this.beverage = beverage;
 }
  
 @Override
 public String getDescription() {
  return beverage.getDescription() + ", SteamMilk";
 }
 
 @Override
 public double cost() {
  return .10 + beverage.cost();
 }
 
}
 
 
public class Whip extends CondimentDecorator {
 Beverage beverage;
  
 public Whip(Beverage beverage) {
  this.beverage = beverage;
 }
  
 @Override
 public String getDescription() {
  return beverage.getDescription() + ", Whip";
 }
 
 @Override
 public double cost() {
  return .10 + beverage.cost();
 }
 
}
 
 
public class StarbuzzCoffee {
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  Beverage beverage = new Espresso();
  System.out.println(beverage.getDescription() + " $" + beverage.cost());
   
  // 아래와 같은 식으로 기본 음료에 첨가물들을 Wrapping해서 가격과 설명을 추가할 수 있다.
  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());
 }
 
}




3. JAVA I/O


(1) 데코레이터 패턴을 이용한 java.io 패키지
1) InputStream은 추상 구성요소(Abstract Component)이며, FileInputStreamStringBufferInputStream,ByteArrayInputStreamObjectInputStream 등이 데코레이터로 Wrapping될 Concrete Component 역할을 하며 InputStream을 상속하고 있다. 그리고 FilterInputStream이 바로 추상 데코레이터(Abstract Decorator)이며 구상 구성요소(Concrete Component)들을 Wrapping 하기 위해 InputStream과 형식을 동일하게 해야 하기 때문에 InputStream을 상속한다.
 PushbackInputStream, BufferdInputStreamDataInputStreamLineNumberInputStream 등은 구상 데코레이터(Concrete Decorator)들이다.
2) OutputStream의 InputStream의 디자인도 똑같고, Reader/Writer Stream(문자 기반의 데이터를 처리하기 위한 Stream)도 거의 유사하게 디자인 되어 있다.

(2) 자바 I/O 데코레이터 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.io.*;
 
// InputStream을 상속한 Abstract Decorator인 FilterInputStream을 상속한다.
public class LowercaseInputStream extends FilterInputStream {
 
 protected LowercaseInputStream(InputStream in) {
  super(in);
 }
  
 @Override
 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 {
  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 {
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  int c;
  try {
   // FileInputStream을 만들고 BufferedInputStream과 새로 만든 LowercaseInputStream으로 감싼다.
   InputStream in = new LowercaseInputStream(new BufferedInputStream(new FileInputStream("test.txt")));
    
   while((c = in.read()) >= 0) {
    System.out.print((char)c);
   }
    
   in.close();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 
}



4. 데코레이터 패턴의 단점


(1) 데코레이터 패턴을 사용하면 자잘한 객체들이 매우 많이 추가될 수 있고, 데코레이터를 너무 많이 사용하면 코드가 필요 이상으로 복잡해질 수도 있다. 

(2) 구성요소(Component)를 초기화하는 데 필요한 코드가 훨씬 복잡해진다.
 구성요소(Component) Instance만 만든다고 해서 일이 끝나는게 아니라 많은 데코레이터로 Wrapping 해야 하는 경우가 있다. 그래서 데코레이터 패턴은 팩토리 패턴이나 빌더 패턴과 함께 사용된다.

(3) 데코레이터 패턴은 구상 구성요소(Concrete Component)_의 형식을 알아내서 그 결과를 바탕으로 어떤 작업을 처리하는 코드(특정 형식에 의존하는 클라이언트 코드)에는 적용할 수 없다. 
 위에 있는 데코레이터 패턴 예시에서 HouseBlend를 데코레이터로 감싸게 되면, 그 커피가 HouseBlend인지 아닌지를 알 수가 없게 된다. 그러므로, 구상 구성요소(Concrete Component)를 바탕으로 돌아가는 코드를 만들어야 한다면, 애플리케이션 디자인 자체 및 데코레이터 패턴을 사용하는 것에 대해서 다시 한번 생각해 볼 필요가 있다.






참고
Head First Design Pattern : 스토리가 있는 패턴 학습법
http://www.gurubee.net/pages/viewpage.action?pageId=1507398




출처 - http://digitanomad.blogspot.kr/2013/01/decorator-pattern.html

'디자인패턴' 카테고리의 다른 글

팩토리 패턴(Factory Pattern)  (0) 2014.11.11
옵저버 패턴(Observer Pattern)  (0) 2014.11.11
어댑터 패턴(Adapter Pattern)  (0) 2014.11.11
싱글톤 패턴  (0) 2012.11.02
데코레이터 패턴  (0) 2012.11.02
: