반응형
(본 강의 노트는 한빛 미디어의책을 기반으로 하고 있습니다)
03 데코레이터 패턴
Decorator Pattern
목적
- 객체에 추가적인 책임을 동적으로 부여함.
- 데코레이터는 서브클래싱(상속)을 사용하지 않아도 유연하고 융통성 있는 기능 확장을 가능하게 함
문제
- 조금씩 기능을 추가하기 위해 새로운 클래스를 생성하는 경우
- 상속으로 문제를 풀면 너무 많은 상속 관계가 발생할 수 있음
- 상속을 사용하지 않고 새로운 기능을 추가할 수 있는가?
디자인 패턴 요소
요소 |
설명 |
이름 |
데코레이터(Decorator) |
문제 |
조금씩 다른 다양한 종류. 늘어날수록 확장 어려움 |
해결방안 |
상속을 사용하지 않고 연관으로 필요한 기능 추가. 실행시점 확장 Extension at runtime (not compile time) |
결과 |
확장성 |
데코레이터 패턴 정의
- 데코레이터 패턴에서는 객체에 추가적인 요건을 동적으로 첨가
- 서브클래스를 만드는 것을 통해 기능을 유연하게 확장할 수 있는 방법을 제공
- 상속을 사용은 하나, 무분별하게 사용하지 않고 연관으로 필요한 기능을 추가
- 데코레이터에서는 새로운 메소드를 추가할 수 있다
- 일반적으로 새로운 메소드를 추가하는 대신 컴포넌트에 원래 있던 메소드 호출 전,또는 후에 별도의 작업을 처리하는 방식으로 새로운 기능을 추가
- 데코레이터 패턴에서의 상속은 기능을 물려 받기 위함이 아닌 형식을 맞추기 위해서임
- 컴포넌트는 추상 클래스 또는 인터페이스로 구현 가능
- 데코레이터 클래스 다이어그램
- Component : 각 구성요소는 직접 쓰일 수도 있고, 데코레이터로 감싸져서 쓰일 수도 있음
- ConcreteComponet : 새로운 행동을 동적으로 추가
- Decorator는 자신이 장식할 구성요소와 같은 인터페이스 또는 추상클래스를 구현
- ConcreteDecorator에는 그 객체가 장식하고 있는 것을 위한 인스턴스 변수가 있음
- Decorator Component의 상태를 확장할 수 있음
사례1: 스타버즈 커피
: 스타버즈 커피는 급속도로 성장한 초대형 커피 전문점
- 급성장하다 보니 다양한 음료들을 모두 포괄하는 주문 시스템을 갖추려고 함
- 초기 시스템은 아래와 같음
- 커피 주문 시, 사람에 따라 여러 토핑을 얹을 수 있음, 이 경우 가격이 토핑 별로 추가됨
- 문제
: 관리의 어려움 -> 즉, 상속이 남용되는 경우가 생길 수 있음
- 새로운 토핑이 추가된다면?
- 기존에 들어가는 재료의 가격이 인상된다면?
- 멤버 변수를 사용하고 상속받는다면?
- Beverage의 cost()에서는 추가되는 토핑 가격의 합을 계산
- 자식 클래스의 cost()에서는 음료 가격 추가
- 문제점?
- 새로운 토핑의 추가시 부모 클래스인 Beverage가 바뀌어야 함
- 불필요한 정보 포함 : 토핑+음료의 조합이 절대 가능성이 없는데 정의해두었음 (아이스티에 휘핑 추가)
- 데코레이터 패턴 적용
- 특정 음료에서 시작해서 첨가물로 그 음료를 장식
- 예: 모카 + 휘핑크림을 추가한 다크로스트 커피 주문
- 다크로스트 객체 가져옴
- 모카 객체로 장식
- 윕 객체로 장식
- cost()메소드 호출(첨가물의 가격을 계산하는 일은 해당 객체들에 위임됨) : 모카, 휩 등은 Wrapper class 임
- Beverage는 구성요소를 나타내는 컴포넌트 추상 클래스 같은 개념으로 볼 수 있음
- 코드
public abstract class Beverage {
String description = "제목 없음";
public String getDescription() {
return description;
}
public abstract double cost();
}
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
public class Espresso extends Beverage {
public Espresso() {
description = "에스프레소";
}
public double cost() {
return 1.99;
}
}
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "하우스 블렌드 커피";
}
public double cost() {
return .89;
}
}
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", 모카";
}
public double cost() {
return beverage.cost() + .20;
}
}
public class StarbuzzCoffee {
public static void main(String[] args[]) {
Beverage b = new Espresso();
System.out.println(b.getDescription()
+ " $" + b.cost());
Beverage b2 = new DarkRoast();
b2 = new Mocha(b2);
b2 = new Mocha(b2); // 모카 한 개 더 추가
b2 = new Whip(b2);
System.out.println(b2.getDescription()
+ " $" + b2.cost());
Beverage b3 = new HouseBlend();
b3 = new Soy(b3);
b3 = new Mocha(b3);
b3 = new Whip(b3);
System.out.println(b3.getDescription()
+ " $" + b3.cost());
}
}
사례2 : 자바 I/O
- 자바의 입출력은 입출력 패키지에서 처리하고, 다음 4개의 클래스를 중심으로 데코레이터 패턴을 사용
- 아래 클래스들은 데코레이터의 구상요소로 쓰이며, 추상 클래스라서 직접 사용할 수 없음
구분
입력
출력
바이트
InputStream
OutputStream
문자
Reader
Writer
-
FileInputStream 구성요소
-
BufferedInputStream 데코레이터
-
입력된 내용을 버퍼에 저장
-
입력 내용을 한 줄 씩 읽을 수 있게 readLine() 제공
-
-
LineNumberInputStream 데코레이터 : 줄 번호를 붙여줌
클래스 다이어그램
반응형
'Computer Science > 디자인패턴' 카테고리의 다른 글
[Head First Design Patterns] 04 팩토리 패턴 (0) | 2020.10.20 |
---|---|
[Head First Design Patterns] 01 디자인 패턴 소개 (0) | 2020.10.18 |
Introduction Advanced OOP (0) | 2020.10.18 |
[Head First Design Patterns] 02 옵저버 패턴 (0) | 2020.09.25 |
SOLID 원칙 (0) | 2020.09.12 |