기존코드를 바꾸지 않고 객체에 새로운 임무를 추가할 수 있다.
상속으로 기능을 추가하기 VS 구성(데코레이터)으로 기능 추가하기
상속으로 서브 클래스에 기능을 구현하려면, 모든 서브 클래스가 오버라이드 해야 한다. 따라서 기존 코드를 수정할 수 밖에 없다.
클래스가 많아지면 일부 서브 클래스에는 적합하지 않은 불필요한 기능까지 상속받는다.
상속으로 기능 추가는 컴파일 시점에 완전히 결정된다.
구성으로 기능을 추가하면, 기존 코드를 건들지 않고, 기능을 추가할 수 있다.
기존 코드를 건드리지 않으므로, 버그 발생 범위가 제한된다.
구성은 런타임 시점에 기능을 동적으로 변경할 수 있다.
자바는 단일 상속 언어로 일반적으로 구성이 옳다고 여겨진다.
OCP 살펴보기
디자인 원칙
클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다.
풀어 말하면, 기존 코드를 수정하지 않으면서 기능 추가가 가능해야 한다는 것
이 점에서 데코레이터 패턴은 OCP를 완전히 준수한다.
원 코드는 건들지 않은채(클라이언트 입장에선 데코레이터 존재조차 모른다) 기능을 추가할 수 있다.
코드에서 확장해야 할 부분을 선택할 때는 세심한 주의를 기울여야 한다.
무조건 OCP를 적용한다면 괜히 쓸데없는 일을 하며 시간을 낭비할 수 있다. 또한 필요 이상으로 복잡하고 이해하기 힘든 코드를 만들게 되는 부작용이 발생할 수 있다.
데코레이터 패턴 살펴보기
데코레이터의 슈퍼클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 같다.
한 객체를 여러 개의 데코레이터로 감쌀 수 있다.
데코레이터는 자신이 감싸고 있는 객체와 같은 슈퍼 클래스를 가지고 있기에 원래 객체가 들어갈 자리에 데코레이터 객체를 넣어도 상관없다.
데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 일 말고도 추가 작업을 수행할 수 있다.
객체는 언제든지 감쌀 수 있으모로 실행 중에 필요한 데코레이터를 마음대로 적용할 수 있다.
데코레이터 패턴 예시
public class DecoratorTest {
//Compenet
static abstract class Beverage{
String decription = "제목 없음";
public String getDecription() {
return decription;
}
public abstract double cost();
}
/**
* Decorator
* 형태만 (다형성) 맞추기 위해 상속(구현)하지만,
* 행동은 구성와 위임으로 런타임 시 동적으로 변경 가능하다.
*/
static abstract class CondimentDecorator extends Beverage{
//다형성
Beverage beverage;
@Override
public abstract String getDecription();
}
static class Espresso extends Beverage{
public Espresso() {
super.decription = "에스프레소";
}
@Override
public double cost() {
return 1.99;
}
}
static class HouseBlend extends Beverage{
public HouseBlend() {
super.decription = "하우스 블렌드 커피";
}
@Override
public double cost() {
return .89;
}
}
static class DarkRoast extends Beverage{
public DarkRoast() {
super.decription = "다크로스트 커피";
}
@Override
public double cost() {
return .99;
}
}
static class Decaf extends Beverage{
public Decaf() {
super.decription = "디카페인 커피";
}
@Override
public double cost() {
return 1.05;
}
}
static class Mocha extends CondimentDecorator{
public Mocha(Beverage beverage) {
super.beverage = beverage;
}
@Override
public String getDecription() {
return beverage.getDecription() + ", 모카";
}
@Override
public double cost() {
return beverage.cost()+ .20;
}
}
static class Milk extends CondimentDecorator{
public Milk(Beverage beverage) {
super.beverage = beverage;
}
@Override
public String getDecription() {
return beverage.getDecription() + ", 우유";
}
@Override
public double cost() {
return beverage.cost()+.10;
}
}
static class Whip extends CondimentDecorator{
public Whip(Beverage beverage) {
super.beverage = beverage;
}
@Override
public String getDecription() {
return beverage.getDecription() + ", 휘핑크림";
}
@Override
public double cost() {
return beverage.cost()+.10;
}
}
static class Soy extends CondimentDecorator{
public Soy(Beverage beverage) {
super.beverage = beverage;
}
@Override
public String getDecription() {
return beverage.getDecription() + ", 두유";
}
@Override
public double cost() {
return beverage.cost()+.15;
}
}
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDecription()+" $"+beverage.cost());
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDecription()+" $"+beverage2.cost());
Beverage beverage3 = new HouseBlend();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDecription()+" $"+beverage3.cost());
}
}
에스프레소 $1.99
다크로스트 커피, 모카, 모카, 휘핑크림 $1.49
하우스 블렌드 커피, 두유, 모카, 휘핑크림 $1.34
java.io 클래스와 데코레이터 패턴
java.io 패키지는 데코레이터 패턴을 바탕으로 만들어졌다.
io 패키지에서 FilterInputStream, FilterOutputStream을 서브 클래스들이 데코레이터 패턴이 적용된 클래스이다.
따라서 FilterInputStream, FilterOutputStream 서브클래스들은 단속으로 사용할 수 없다.
문자열 기반 IO스트림은 FilterWriter, FilterReader 도 똑같다.
데코레이터의 단점은 사용 시 클래스가 많아진다. 그리고 중첩 사용 시 중첩 구조를 파악하기 힘든다.
자바 I/O 데코레이터 만들기
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
package designpattern.structural.decorator;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class IOStream {
public static void main(String[] args) throws FileNotFoundException {
try(InputStream in =
new UpperCaseInputStream(
new BufferedInputStream(
//자기에 맞는 프로젝트명 패키지 경로 파일 입력 필요
new FileInputStream("src/designpattern/structural/decorator/text.txt")));)
{
StringBuilder sb = new StringBuilder();
for(int c=0; (c=in.read()) != -1;) {
sb.append((char)c);
}
System.out.println(sb);
} catch (IOException e) { }
}
static class UpperCaseInputStream extends FilterInputStream{
public UpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int data = in.read();
return data == -1 ? data : Character.toUpperCase(data);
}
}
}
결과
LOREM IPSUM IS SIMPLY DUMMY TEXT OF THE PRINTING AND TYPESETTING INDUSTRY. LOREM IPSUM HAS BEEN THE INDUSTRY'S STANDARD DUMMY TEXT EVER SINCE THE 1500S, WHEN AN UNKNOWN PRINTER TOOK A GALLEY OF TYPE AND SCRAMBLED IT TO MAKE A TYPE SPECIMEN BOOK. IT HAS SURVIVED NOT ONLY FIVE CENTURIES, BUT ALSO THE LEAP INTO ELECTRONIC TYPESETTING, REMAINING ESSENTIALLY UNCHANGED. IT WAS POPULARISED IN THE 1960S WITH THE RELEASE OF LETRASET SHEETS CONTAINING LOREM IPSUM PASSAGES, AND MORE RECENTLY WITH DESKTOP PUBLISHING SOFTWARE LIKE ALDUS PAGEMAKER INCLUDING VERSIONS OF LOREM IPSUM.
핵심 정리
디자인 유연성 면에선 상속보단 구성(과 위임)이 더 좋다. (OCP)
기존 코드 수정 없이 행동을 확장할 수 있다.
구성은 런타임 시점에 기능을 동적으로 변경할 수 있다.
인터페이스로 설계 시 클라이언트는 데코레이터 존재를 알 수 없다.
데코레이터 패턴을 사용하면 코드가 다소 복잡해진다.
'IT책, 강의 > 헤드 퍼스트 디자인 패턴' 카테고리의 다른 글
CHAPTER 06 호출 캡슐화하기 커맨드 패턴 (0) | 2023.05.24 |
---|---|
CHAPTER 05 하나뿐인 특별한 객체 만들기 (싱글턴 패턴) (0) | 2023.05.21 |
CHAPTER 04 객체지향 빵 굽기 팩토리 패턴 (0) | 2023.05.19 |
CHAPTER 02 객체들에게 연락 돌리기 옵저버 패턴 (0) | 2023.05.15 |
CHAPTER 01 디자인 패턴 소개와 전략 패턴 (0) | 2023.01.23 |