////
Search
Duplicate
🎭

Chapter 03. Decorator

특정한 객체에 대해 기능 확장이나 변경이 필요할때, 상속 대신 객체를 감싸는 방식으로 구현하는 구조적 디자인 패턴이다.
다음과 같이 치킨이라는 객체가 있다고 생각해보자.
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개의 객체만 존재하지만 HotFriedFried에 대한 객체들도 상속받아 구현해줘야할 것이다.
그렇다면 새로운 메뉴가 추가된다면? 우린 해당 객체를 생성하고 또 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이며 BreadBburingcle, HotFried가 해당한다.
IngredientOption이 해당하며 ShirmpsPattyWithBone, WithoutBone 등이 해당한다.
위와 같이 구현했을 때, 다음과 같은 객체지향적 장점이 생긴다.
1.
데코레이터를 사용하면 서브 클래스를 이용한 상속보다 더 유연하게 기능을 확장할 수 있다.
2.
객체를 여러 데코레이터로 래핑하여 여러 동작을 결합할 수 있다.
3.
런타임에 동적으로 기능을 변경할 수 있다.
4.
각 데코레이터 클래스마다 고유의 책임을 가져가게 되므로 SRP를 준수한다.
5.
기존 코드 수정없이 기능 확장이 가능하므로 OCP도 준수하게 된다.
6.
구현체가 아닌 인터페이스를 바라보므로써 DIP도 준수하게 된다.

사용 시기

: 객체 책임과 행동에 동적으로 상황에 따라 다양한 기능이 빈번하게 추가되거나 삭제되는 경우
: 객체의 결합으로 기능을 정의할 수 있는 경우
: 상속을 통해 서브클래스를 생성하여 객체의 동작을 확장하는 것이 생산성 저하로 이어질 수 있다고 판단하는 경우

주의점

: 장식자 일부가 필요없어지는 경우, 래핑을 제거하는 과정이 번거롭다.
: 데코레이터를 조합하는 코드가 조금 더럽다.
: 데코레이터 순서가 존재하므로 순서에 의존하지 않는 작업을 구현하기는 어렵다.