데코레이터 패턴(Decorator Pattern)
디자인패턴 2014. 11. 11. 14:191. 데코레이터 패턴이란?
데코레이터 패턴에서는 객체에 추가 요소를 구성(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)이며, FileInputStream, StringBufferInputStream,ByteArrayInputStream, ObjectInputStream 등이 데코레이터로 Wrapping될 Concrete Component 역할을 하며 InputStream을 상속하고 있다. 그리고 FilterInputStream이 바로 추상 데코레이터(Abstract Decorator)이며 구상 구성요소(Concrete Component)들을 Wrapping 하기 위해 InputStream과 형식을 동일하게 해야 하기 때문에 InputStream을 상속한다.
PushbackInputStream, BufferdInputStream, DataInputStream, LineNumberInputStream 등은 구상 데코레이터(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 |