장점
단순한 상속보다 설계의 융통성을 증가
장식자 패턴은 객체에 새로운 행동을 추가할 수 있는 가장 효과적 방법, 장식자를 사용하면 장식자를 객체와 연결하거나 분리하는 작업을 통해 새로운 책임을 추가하거나 삭제하는 일이 런타임에 가능해짐.
반면, 상속
에서는 정적으로 새로운 클래스를 추가해야만 추가적인 행동을 정의할 수 있음.
클래스 계통의 상부측 클래스에 많은 기능이 누적되는 상황을 피할 수 있음.
장식자 패턴은 책임 추가 작업에서 필요한 비용만 그때 지불하는 방법을 제공. 지금 예상하지 못한 특성들을 한꺼번에 다 개발하기 위해 고민하고 노력하기 보다는 발견하지 못하고 누락된 서비스들을 Decorator 객체를 통해 지속적으로 추가
단점
장식자와 해당 그 장식자의 구성요소가 동일한 것은 아님
장식자는 사용자에게 일관된 인터페이스를 제공하는 껍데기. 그러므로 객체 식별자 관점에서 구성요소와 이를 둘러싼 Decorator 객체가 동일한 식별자를 가질 필요가 없음
장식자를 사용함으로써 작은 규모의 객체들이 많이 생김
장식자 패턴을 사용하는 설계에서는 규모가 작은 객체들의 수가 많아지는데, 이 객체들이 서로 다른 점은 상호작용하는 방법에 있지, 클래스가 다르거나 변수에 정의된 값이 다른 것은 아님.
구현법
인터페이스 일치시키기 : Decorator 객체의 인터페이스는 반드시 자신을 둘러싼 구성요소의 인터페이스를 만족, 따라서 ConcreteDecorator 클래스는 동일한 부모클래스 상속
추상 클래스로 정의되는 Decorator 클래스 생략하기 : 간혹 추상 클래스인 Decorator 클래스를 정의할 필요가 없을때도 있습니다. 이때는 Decorator 클래스에 정의할 책임이 한가지 밖에 존재하지 않음. 이것은 새로운 클래스들을 설계할 때 발생하기 보다는 기존에 존재하는 클래스 계통에 자주 일어남, 따라서 구성요소에게 요청을 전달하는 Decorator 클래스의 책임을 ConcreteDecorator와 합칠수 있음.
Component 클래스는 가벼운 무게를 유지하기 : 인터페이스를 만족하는지 확인하려면 구성요소와 Decorator 모두 동일한 부모 클래스인 Component 클래스를 상속받아야함. 가볍게 정의한다는 의미는 연산에 해당하는 인터페이스만을 정의하고 무언가 저장할 수 있는 변수는 정의하지 말라는 의미
객체의 겉포장을 변경할 것인가, 속을 변경할 것인가? : 흔히 장식이란 행동을 변경할 수 있도록 객체에 외장을 입힌 것으로 알고 있는데, 이렇게 겉만 바꾸는 것이 아니라 내부도 변경할 수 있는데, 내부를 변경하는 대표적인 예가 뒤에서 살펴볼 전략 패턴.
Component 클래스가 무겁다면 전략패턴
그 반대라면 Decorator 패턴을 사용할 수 있을 것 같다.
abstract public class Beverage {
String description = "제목 없음";
public String getDescription() {
return description;
}
public abstract double cost();
}
//에스프레소
public class Espresso extends Beverage{
public Espresso() {
description = "에스프레소";
}
@Override
public double cost() {
return 1.99;
}
}
// 하우스 블렌드
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "하우스 블렌드 커피";
}
@Override
public double cost() {
return .89;
}
}
//decorator들
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
//실제 mocha가 Beveage를 들고 있기 때문에 위 커피종류에 해당하는 모든 기능을 다 사용할 수 있다.
//결국 이부분이 Adapter 패턴이랑 유사한거잖아.
//다만 관점의 차이가 있을 뿐 (같은 뿌리에서 기능을 추가하느냐 : 데코)
//다른 클래스에서 해당 클래스를 호환하기 위해 사용 Adapter
public class Mocha extends CondimentDecorator{
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return .20 + beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription() + ", 모카";
}
}
의도 : 한 서브시스템 내의 인터페이스 집합에 대한 획일화된 하나의 인터페이스를 제공하는 패턴으로, 서브시스템을 사용하기 쉽도록 상위 수준의 인터페이스를 정의
동기
활용성
구조
public class Light {
public void on() {
System.out.println("조명 킵니다.");
}
public void movieMode() {
System.out.println("조명을 영화관 모드로 변경합니다.");
}
}
public class Screen {
public void down() {
System.out.println("스크린을 내립니다.");
}
public void up() {
System.out.println("스크린을 올립니다.");
}
}
public class Projector {
public void on() {
System.out.println("Projector를 킵니다.");
}
public void off() {
System.out.println("Projector를 끕니다.");
}
public void movieMode() {
System.out.println("Projector를 영화관모드로 설정합니다.");
}
}
public class DvdPlayer {
public void on() {
System.out.println("DvdPlayer를 킵니다.");
}
public void play(String movieName) {
System.out.println(movieName + "를 시작합니다.");
}
public void off() {
System.out.println("DvcPlayer를 끕니다.");
}
}
//퍼사드 객체
public class HomeTheaterFacade {
Light light;
Projector projector;
Screen screen;
DvdPlayer dvdPlayer;
public HomeTheaterFacade(Light light, Projector projector,
Screen screen, DvdPlayer dvdPlayer) {
this.light = light;
this.projector = projector;
this.screen = screen;
this.dvdPlayer = dvdPlayer;
}
public void watchMovie(String movieName) {
System.out.println("===============WATCH MOIVE===============");
light.movieMode();
screen.down();
projector.on();
projector.movieMode();
dvdPlayer.on();
dvdPlayer.play(movieName);
}
public void endMovie() {
System.out.println("===============END MOIVE===============");
light.on();
screen.up();
projector.off();
dvdPlayer.off();
}
}
logger.info
와 같이 slf4j가 제공하는 메소드 만으로 로그 기능을 쉽게 사용할 수 있다.아마 이점이 가장 매력적인 것 같다.