•
특정한 객체에 대해 기능 확장이나 변경이 필요할때, 상속 대신 객체를 감싸는 방식으로 구현하는 구조적 디자인 패턴이다.
•
다음과 같이 치킨이라는 객체가 있다고 생각해보자.
public interface Chikcen {
String getDescription();
String getPrice();
}
Java
복사
•
이 추상 클래스를 구현하는 다양한 구체 클래스들이 있다고 생각해보자.
뿌링클 클래스
public class Bburingcle implements Chikcen {
public String description = "뿌링클";
public int price = 19000;
// Mehtods
}
Java
복사
핫후라이드 클래스
public class HotFried implements Chikcen {
public String description = "핫후라이드";
public int price = 18000;
// Mehtods
}
Java
복사
후라이드 클래스
public class Fried implements Chikcen {
public String description = "후라이드";
public int price = 16000;
// Mehtods
}
Java
복사
•
만약 모든 치킨 메뉴에 순살, 뼈 옵션을 넣는다면 어떻게 해야될까?
•
다음과 같이 Bburingcle 클래스를 상속받은 서브 클래스들을 만들어야할까?
public class BburingcleWithBone extends Bburingcle {
public String description = "뿌링클 뼈";
public int price = 19000;
public boolean isBone = True;
// Methods
}
Java
복사
public class BburingcleWithBone extends Bburingcle {
public String description = "뿌링클 뼈";
public int price = 19000;
public boolean isBone = True;
// Methods
}
Java
복사
•
이렇게 상속을 통해 구현했다. 위에는 단순히 Bburingcle을 상속받은 2개의 객체만 존재하지만 HotFried나 Fried에 대한 객체들도 상속받아 구현해줘야할 것이다.
•
그렇다면 새로운 메뉴가 추가된다면? 우린 해당 객체를 생성하고 또 WitoutBone, WithBone이 붙은 서브 클래스를 작성해주어야할 것이다.
⇒ 뭐 메뉴가 적다면 충분히 고려할 수도 있는 방안이긴 하지만 우린 새로운 메뉴를 수없이 만들어낼 것이라고 가정해보자.
•
그럼 이런 상황에서 더 적은 코드로 확장성 있게 작성하려면 어떻게 해야할까?
•
Chicken 인터페이스를 필드 변수로 가지는 래퍼 인터페이스를 선언해주고 해당 인터페이스를 구현하는 객체들을 만들면 된다.
public interface Option extends Chikcen {
String getDescription();
int getPrice();
}
Java
복사
public class WithBone implements Option {
public Chikcen chikcen;
public WithBone(Chikcen chikcen) {
this.chikcen = chikcen;
}
public String getDescritpion() {
log.info("뼈 치킨입니다.");
return chikcen.getDescription() + ", 뼈";
}
public int getPrice() {
return chikcen.getPrice();
}
}
Java
복사
public class WithoutBone implements Option {
public Chikcen chikcen;
public WithBone(Chikcen chikcen) {
this.chikcen = chikcen;
}
public String getDescritpion() {
log.info("순살 치킨입니다.");
return chikcen.getDescription() + ", 순살";
}
public int getPrice() {
return chikcen.getPrice() + 2000;
}
}
Java
복사
•
각각 치킨 메뉴를 꾸며주는 작업을 수행한다. 또한 로깅을 남긴다던지별도로 다른 작업을 수행할 수도 있다.
•
위 그림을 봐보자. 위 그림에서 Hamburger는 예시 코드의 Chikcen이며 Bread는 Bburingcle, HotFried가 해당한다.
◦
Ingredient는 Option이 해당하며 ShirmpsPatty는 WithBone, WithoutBone 등이 해당한다.
•
위와 같이 구현했을 때, 다음과 같은 객체지향적 장점이 생긴다.
1.
데코레이터를 사용하면 서브 클래스를 이용한 상속보다 더 유연하게 기능을 확장할 수 있다.
2.
객체를 여러 데코레이터로 래핑하여 여러 동작을 결합할 수 있다.
3.
런타임에 동적으로 기능을 변경할 수 있다.
4.
각 데코레이터 클래스마다 고유의 책임을 가져가게 되므로 SRP를 준수한다.
5.
기존 코드 수정없이 기능 확장이 가능하므로 OCP도 준수하게 된다.
6.
구현체가 아닌 인터페이스를 바라보므로써 DIP도 준수하게 된다.
사용 시기
: 객체 책임과 행동에 동적으로 상황에 따라 다양한 기능이 빈번하게 추가되거나 삭제되는 경우
: 객체의 결합으로 기능을 정의할 수 있는 경우
: 상속을 통해 서브클래스를 생성하여 객체의 동작을 확장하는 것이 생산성 저하로 이어질 수 있다고 판단하는 경우
주의점
: 장식자 일부가 필요없어지는 경우, 래핑을 제거하는 과정이 번거롭다.
: 데코레이터를 조합하는 코드가 조금 더럽다.
: 데코레이터 순서가 존재하므로 순서에 의존하지 않는 작업을 구현하기는 어렵다.