장식자 패턴(Decorator)
장식자 패턴
객체에 동적으로 새로운 행위를 추가할 수 있도록 해주는 장식 패턴
상속과 포함을 모두 사용하여 구현한다.
[장식자 패턴 사용 환경]
상속을 통해 객체의 책임을 확장하기 어려울 경우
실행시간에 객체의 행위를 추가하고싶다.
객체에 추가할 수 있는 서로 독립적인 다양한 기능들이 존재한다.
자유롭게 객체에 기능을 추가하고 제거하고 싶을 경우
[장점]
실행시간에 유연하게 객체에 기능 상태를 추가 제거 할수있다.
클래스의 높은 응집력(SRP)
[단점]
클래스가 많아질수있으며 디버깅이 힘들어질수있다.
특정 제거와 제약이 힘들수있다.
[관련패턴]
DIP 생성패턴
클래스 폭발 문제를 완화시킬수있다.
커피같이 첨가물이 포함되는 주문 시스템을 만든다고 할때 비슷한 클래스가 무수히 늘어나는 현상
커피+초코추가 , 커피+우유추가 , 커피+쿠키추가 , 커피+우유+쿠키추가 등
비슷한 클래스가 엄청나게 발생
해결시도 1. 모든 추가옵션 맴버로 유지
안쓰는 추가옵션은 의미없는 선언 = 메모리낭비 ISP위배
변경발생시 관련코드 모두 직접수정해야함 OCP위배
추가사항이 적합하지 않을수있음 LSP위배
해결시도 2. 상속
행위는 컴파일시간에 고정, 실행시간에 동적으로 추가 불가 또한 모든 자식 클래스는 동일한 행위를 무조건 상속받아야 함
수정발생시 슈퍼클래스를 수정해야함
해결시도 3. 상속 + 포함이용 = ''데코레이트 패턴''
has-a: 객체에 행위를 실행시간에 추가할 수 있음
상속: 장식된 객체를 원래 객체 대신하여 사용할수있도록함
장식 대상 추상 클래스 또는 interface:
장식된 객체를 조작할 때 사용하는 리모컨
때문에 장식자들도 이를 상속해야한다.
장식자 추상클래스:
꼭 필요하진 않을수있지만 재정의 명시기능과 장식자와 장식 대상자를 구분할수있는 구분자로 활용가능
장식자 구체화 클래스:
장식 대상을 has-a로 유지
장식대상은 장식되어있을수도있다. 장식대상 추상클래스를 최상위 공통리모컨 타입으로 이용 하기때문에 장식여부에 상관없이 has-a로 받을수있어야한다.
[코드]
public abstract class Beverage { private String description = "이름없는 음료"; public void setDescription(String description) { this.description = description; } public String getDescription() { return description; } public abstract int cost(); } /** Beverage class 가장 상위의 슈퍼 클래스인 Beverage 클래스 입니다. 음료의 공통적인 성질을 따로 뺀 것으로 카페에서 판매하는 모든 음료 및 첨가물은 이 클래스를 상속 받아야 합니다. */
public abstract class CondimentDecorator extends Beverage { //abstract cost(); // CondimentDecorator도 추상 클래스라 Beverage 추상 메서드 재정의할 필요 X public abstract String getDescription(); //Beverage getDescription을 추상 메서드로 변경 //첨가물 구현체들에게 각각 overriding 위임 } /** CondimentDecorator class 모든 첨가물들이 상속 받아야 하는 클래스 입니다. */
public class DarkRoast extends Beverage { private static final int COST = 1200; public DarkRoast(){ setDescription("다크로스트 커피"); //super.setDescription } @Override public int cost() { return COST; } } /** 판매하는 음료: "장식될 객체" */
- 꾸며질 객체
// CondimentDecorator + Beverage 상속 //장식자임을 CondimentDecorator를 통해 확인가능 public class Whip extends CondimentDecorator { private Beverage beverage; public Whip(Beverage beverage){ this.beverage = beverage; } @Override public String getDescription() { //이름 장식 return beverage.getDescription()+", 크림"; } @Override public int cost() { //가격 장식 return beverage.cost()+500; } } /** 첨가물: 장식될 객체 첨가물 각각 메서드들을 생성하여 다양한 구현을 할수있다. */
- 장식할 객체: 꾸며질 객체를 감싼다.
[메인]
//클라이언트가 사용할 메인 테스트 코드 public class CoffeeTest { public static void main(String[] args) { Beverage beverage = new DarkRoast(); //공통리모컨 abstract beverage 사용 // 피장식자 DarkRoast 주입 beverage = new Mocha(beverage); //DarkRoast 객체에 Mocha 장식자 덧씌우기 beverage = new Milk(beverage); //DarkRoast+Mocha 객체에 Milk 장식자 덧씌우기 System.out.printf("%s: %,d원%n", beverage.getDescription(), beverage.cost()); //장식된 객체를 원래 객체를 대신하여 사용 가능 } }
- 메인코드
[객체가 꾸며지는 모습]
데코레이터 패턴을 이용하지않고 추가객체들을 컬렉션 리스트로 유지할 수 도있다.
다만 데코레이터 패턴은 포장과정에서 메서드를 다양하게 재정의 하여 다채로운 구현이 가능하다.
복잡한 방식이 필요한 경우 ''데코레이터 패턴''이 ''컬렉션 리스트''를 사용하는것보다 유리할것
제한이나 제약조건이 복잡한 경우 생성패턴으로 설계하는게 유리할수있다.
데코레이션 패턴을 적용하면 제약조건을 부여할때 코드중복,응집력약화 등의 문제가 발생한다.
이땐 데토레이터 패턴보단 생성패턴을 활용해 설계하는게 더 좋은 방법이 될수있다.