Decorator Pattern
같은 class의 다른 객체들에는 영향을 주지 않고,
구성과 위임을 통해 특정 객체에 행동을 동적으로 추가
- 소스를 변경하지 않고 기능을 확장
- 기존 객체의 메소드에 새로운 행동을 추가하거나 오버라이딩 가능
- 객체에 독립적인 기능을 계속해서 추가, 확장할 때 유용
- 객체에 부가적인 기능을 동적으로 추가할 때 사용
문제점)
- 상속을 사용한 객체의 기능 확장은 subclass를 양산한다.
- Subclassing을 사용한 정적 확장의 문제점:
① 너무 많은 subclass가 필요함
② 새로운 유형의 음료수가 개발될 때마다 subclass를 계속 추가되어야 함.
패턴 아이디어)
DataInputStream dis; // primitive value
dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream(dataFile)
)
);
사용자는 connect stream 객체를 filter stream(장식) 객체들로 포장(랩핑)하여
원하는 기능을 가진 스트림 객체를 동적으로 만들어낸다.
실례)
윈도우 기능이나 I/O Stream
java 라이브러리 개발에도 많이 쓰인다.
Wrapping을 통해 객체의 책임을 동적으로 확장
: 바이트 입력 + 버퍼링 + 타입 변환
//기본 객체:
FileInputStream fis = new FileInputStream(dataFile);
//장식 객체들:
BufferedInputStream bis = new BufferedInputStream( fis ); //⇦ COMPOSITION of fis
DataInputStream dis = new DataInputStream( bis ); //⇦ COMPOSITION of bis
구성과 위임을 통해 추가적인 연산들을 실행
사용자는 connect stream 객체를 filter stream 객체들로 포장하여
원하는 기능을 가진 스트림 객체로
파라미터를 통해 객체(기능)을 동적 추가(확장)
Java I/O Streams
Java I/O 는 Decorator 패턴의 downsides 중 하나이다.
장식 객체는 input stream으로 uppercase에서 lowercase로 변환한다.
Decorator Pattern Diagram
composite pattern 유사
DIP 적용
Component : 추상화 클래스
ConcreteComponent : 기본 구현 클래스
Decorator : 장식(옵션) 추상 클래스, 최상위 추상화 클래스 상속, 상위 클래스 구성
ConcreteDecorator : 기본 클래스에 행동을 추가 구현
구현 예제
- 추상클래스-추상클래스-구상클래스
- 인터페이스-추상클래스-구상클래스
(계약, 코드 공유(분류), 구상)
추상클래스 활용
Component
public abstract class Beverage
{
String description = "Unknown Beverage";
public String getDescription(){
return description;
}
public abstract double cost();
}
ConcreteComponent
public class Espresso extends Beverage
{
public Espresso() {
description = "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
Decorator
public abstract class CondimentDecorator extends Beverage
{
Beverage beverage;
public CondimentDecorator (Beverage beverage) {
this.beverage = beverage; //객체 구성
}
public abstract String getDescription();
}
ConcreteDecorator
public class Milk extends CondimentDecorator
{
Beverage beverage;
public Milk(Beverage beverage) {
super(beverage);
this.beverage = beverage; //객체 구성
}
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
public double cost() {
return .10 + beverage.cost();
}
}
메인 실행문
public class StarbuzzCoffee {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()
+ " $" + beverage.cost());
Beverage beverage2 = new Espresso();
beverage2 = new Milk(beverage2);
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());
}
}
실행결과
인터페이스 활용
// Revised : 추 - 추 - 구상
// cf : interface - abstract - concrete
// Component
public interface Beverage
{
public String getDescription();
public double cost();
}
// 첨가물
// Decorator
public abstract class CondimentDecorator implements Beverage // Component
{
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
}
// ConcreteComponent
public class Espresso implements Beverage // Component
{
private String description = "Espresso";
private double cost = 1.99;
@Override
public String getDescription() {
return description;
}
@Override
public double cost() {
return cost;
}
}
public class HouseBlend implements Beverage
{
private String description = "HouseBlend";
private double cost = 1.99;
@Override
public String getDescription() {
return description;
}
@Override
public double cost() {
return cost;
}
}
// ConcreteDecorator
public class Milk extends CondimentDecorator
{
private String description = " | Milk";
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription()+description;
}
@Override
public double cost() {
return .1+beverage.cost();
}
}
public class Mocha extends CondimentDecorator
{
private String description = " | Mocha";
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription()+description;
}
@Override
public double cost() {
return beverage.cost() + .5;
}
}
Detail
Decorator 객체와 함께 생성
Beverage 를 상속한 DarkRoast(음료 메뉴) 객체로 시작
사용자는 Mocha를 원한다.
그래서 Mocha 객체를 생성하고
DarkRoast 객체에 씌운다.
고객은 휘핑 크림을 원한다.
그래서 휘핑 크림 객체를 생성하고
Mocha 객체에 씌운다.
Whip 의 cost()는 각 객체의 위임을 통해 실행된다.
ß Decorators have the same supertype as the objects they decorate.
ß You can use one or more decorators to wrap an object.
ß Given that the decorator has the same supertype as the object it decorates, we can pass
around a decorated object in place of the original (wrapped) object.
ß The decorator adds its own behavior either before and/or after delegating to the object it
decorates to do the rest of the job.
ß Objects can be decorated at any time, so we can decorate objects dynamically at runtime
with as many decorators as we like.
책 참고 : Head First Design Patterns
댓글