객체를 결합하고 객체에 추가적인 책임을 동적으로 부여하여, 서브 클래스를 만들지 않고 기능을 유연하게 확장할 수 있게 해주는 패턴이다.
Component
(인터페이스 or 추상클래스)Decorator
(Component와 같은 인터페이스 또는 추상 클래스)ConcreteComponent
ConcreteDecorator
(Decorator의 구체적인 행동)스타버즈 커피 주문 어플리케이션
커피 종류
: HouseBlend, DarkRoast, Decaf, Espresso토핑 종류
: 스팀 우유, 두유, 모카, 휘핑 크림 음료 설명
, cost 계산
⭐디자인원칙: 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.
커피 종류
에서 시작해서토핑
으로 장식
모카와 휘핑 크림을 추가한 에스프레소
-> Wrapper class:Mocha
,Whip
에스프레소
는 Beverage
로부터 상속모카
와 휘핑
객체는 데코레이터. 이 객체의 형식은 이 객체가 장식하고 있는 객체(에스프레소
)를 반영하므로 Beverage
임Whip
의 cost()
를 호출Beverage
커피 종류
(HouseBlend
, DarkRoast
, Espresso
, Decaf
)CondimentDecorator
)토핑 종류
(Milk
, Mocha
, Soy
, Whip
)Component
public abstract class Beverage {
String description = "제목 없음";
public String getDescription() { // 음료 설명
return description;
}
public abstract double cost(); // cost 계산 뼈대
}
Decorator
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription(); // '정의'만 구현, 구체적인 행동은 ConcreteDecorator에서.
}
ConcreteComponent
public class Espresso extends Beverage {
public Espresso() {
description = "에스프레소"; // description은 Beverage로부터 상속받음
}
public double cost() {
return 4000; // 가격은 에스프레소 가격만 입력
}
}
ConcreteDecorator
public class Mocha extends CondimentDecorator {
Beverage beverage; // 데코레이션 할 음료를 저장하기 위한 인스턴스 변수
public Mocha(Beverage beverage) {
this.beverage = beverage; // 생성자를 통해 데코레이션 할 음료객체를 전달
}
public String getDescription() {
return beverage.getDescription() + ", 모카"; // 음료 설명에 Mocha 추가
}
public double cost() {
return beverage.cost() + 1000; // 음료 가격에 토핑요금 추가
}
}
Main
public class StarbuzzCoffee {
public static void main(String[] args[]) {
Beverage b = new Espresso();
System.out.println(b.getDescription() + " $" + b.cost());
Beverage b2 = new DarkRoast();
b2 = new Mocha(b2);
b2 = new Mocha(b2); // 모카 한 개 더 추가
b2 = new Whip(b2);
System.out.println(b2.getDescription() + " $" + b2.cost());
Beverage b3 = new HouseBlend();
b3 = new Soy(b3);
b3 = new Mocha(b3);
b3 = new Whip(b3);
System.out.println(b3.getDescription() + " $" + b3.cost());
}
}
InputStream
, OutputStream
,Reader
, Writer
) 를 중심으로 데코레이터 패턴을 사용 바이트 기반 스트림
과 문자 기반 스트림
: 데코레이터의 구성요소로 쓰이며, 추상 클래스라서 직접 사용할 수 없음InputStream
, OutputStream
(바이트단위로 데이터를 전송하는 클래스)FileIntputstream
/FileOutputStream
(파일 입출력에 사용되는 스트림 클래스)BufferedInputStream
/BufferedOutputStream
DataInputStream
/DataOutputStream
Reader
, Writer
FileReader
/FileWriter
BufferedReader
/BufferedWriter
InputStreamReader
/OutputStreamWriter
PrintWriter
바이트 기반 스트림은 바이트단위로 데이터를 전송하는 클래스로 InputStream과 OutputStream을 상속받는 FileStream, ByteArrayStream, PipedStream, AudioStream, StringBufferStream 등이 있다.
FileInputStream
구성요소BufferedInputStream
데코레이터LineNumberInputStream
데코레이터import java.io.FileInputStream;
public class ReadFile {
public static void main(String[] args) {
try {
// 읽기
FileInputStream readme = new FileInputStream("readme.txt");
int b = readme.read(); // 바이트 단위로 읽음
System.out.println("b = " + b);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.FileInputStream;
import java.io.BufferedInputStream;
public class ReadFile {
public static void main(String[] args) {
try {
BufferedInputStream readme
= new BufferedInputStream( // 기반 스트림 생성 후 기반 스트림을 이용한 보조 스트림 생성
new FileInputStream("readme.txt")); // 기반 스트림 생성
int b = readme.read();
System.out.println("b = " + b);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Reader 클래스는 문자 기반 스트림 클래스들이 상속받은 부모 클래스로 byte 배열이 아닌 char 배열을 사용하는 read()가 추상메서드로 구현되어 있다.
import java.io.FileReader;
public class ReadFile {
public static void main(String[] args) {
try {
// 읽기
FileReader readme = new FileReader("readme.txt");
int b = readme.read();
System.out.println("b = " + b);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.FileReader;
import java.io.BufferedReader;
public class ReadFile {
public static void main(String[] args) {
try {
BufferedReader readme = new BufferedReader( // BufferedReader가 FileReader 감싸기
new FileReader("readme.txt"));
String line = readme.readLine(); // readLine()은 BufferedReader에 존재
System.out.println("line = " + line);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.FileReader;
import java.io.LineNumberReader;
public class ReadFile {
public static void main(String[] args) {
try {
LineNumberReader readme = new LineNumberReader( // LineNumberReader를 FileReader 위에 덧씌움
new FileReader("readme.txt"));
String line = readme.readLine();
System.out.println("line " + readme.getLineNumber() + " = " + line);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
입력 스트림에 있는 대문자를 전부 소문자로 바꿔주는 데코레이터
import java.io.FilterInputStream; // 이것을 상속받아서 새로운 데코레이터 만들기
import java.io.InputStream;
import java.io.IOException;
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) { // InputStream = Component
super(in);
}
public int read() throws IOException { // InputStream 안에 read() 존재
int c = super.read();
return ((c == -1) ? c : Character.toLowerCase((char) c));
}
public int read(byte[] b, int offset, int len) throws IOException { // InputStream 안에 read(b, offset, len) 존재
int result = super.read(b, offset, len);
for (int i = offset; i < offset + result; i++) {
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
import java.io.*;
public class InputTest {
public static void main(String[] args) throws IOException {
int c;
try {
InputStream in = new LowerCaseInputStream( // LowerCaseInputStream으로 감싸기
new BufferedInputStream( // BufferedInputStream으로 감싸고
new FileInputStream("test.txt"))); // FileInputStream = 기본 Component
while ((c = in.read()) >= 0) {
System.out.print((char) c);
}
in.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}