3. Decorator Pattern (장식 패턴 )
3.1 Decorator Pattern 이란?
상속을 통한 기능확장이 아니라 구성을 통한 기능확장을 가능하게 해주는 패턴
- Decorator super class의 sub class(decorator)를 만들어 객체를 감쌈으로써 기능을 유연하게 확장할 수 있다.
객체는 언제든지 감쌀 수 있기 때문에 실행중에 필요한 decorator를 마음대로 적용할 수 있다.
(객체에 추가적인 사항을 동적으로 추가할 수 있게 해준다)
(=원하는 기능을 가진 decorator를 미리 만들어 두기만 한다면 실행중 언제든 해당기능을 가진 decorator를 적용해 기능을 추가할 수 있다.)
- decorator들의 super class와 감쌀 객체의 super class는 같다. (같은 형식으로 맞춘다)
- 자신이 감쌀 객체를 레퍼런스로 가진다.
- 한 객체를 여러 개의 decorator로 감쌀 수 있다. 즉, decorator로 decorator객체를 감싸도 된다. (같은 형식이기 때문에)
- decorator는 자신이 장식하는 객체에 어떤 행동을 위임하는 것 외에도 원하는 추가적인 기능을 추가할 수 있다.
즉, 추가되는 기능은 상속을 통해 생기는 것이 아니라 decorator객체와 구성객체간의 구성을 통해 추가된다.
*tip
행동을 물려받기 위한 목적의 상속이 아니라 상위 형식으로 맞추기 위한 상속은 괜찮다.
3.1.1 decorator 객체가 자신이 감쌀 객체와 같은 interface를 가져야하는 이유
자신이 감쌀 객체를 reference(instance variable)로 가지는 decorator객체는 해당 객체 뿐만 아니라
Decorator super class의 모든 sub class 즉, decorator객체들 또한 감쌀 수 있어야 하기 때문이다.
3.1.2 객체 구성
- 인스턴스 변수로 다른 객체를 저장하는 방식
- 상속을 사용한다면 행동이 컴파일시에 정적으로 결정되어 버리고 만다. 즉, super class에서 받은 것, 또는 코드를 통해서 override한 것만 쓸 수 있다.
하지만 객체 구성을 활용하면 실행중에 decorator를 마음대로 조합해서 사용할 수 있다.
- 상속에만 의존한다면 새로운 행동을 추가해야 할 때마다 기존 코드를 바꿔야 하지만, 객체 구성을 사용한다면 언제든지 decorator를 새로 구현해서 새로운 행동을 추가할 수 있다. 구체적으로는 자신이 감싸고 있는 구성요소의 메소드를 호출한 결과에 새로운 기능을 더함으로써 행동을 확장한다.
3.1.2 Decorator Pattern의 단점
특정 형식에 의존하는 클라이언트(사용자)코드를 가져온 경우 decorator pattern 적용이 불가능하다.
구성 요소와 데코레이터는 interface로 형식을 맞추어 디자인하기 때문에 감싼 객체의 구체적인 형식을 알 수 없기 때문이다.
3.1.2 Decorator Pattern을 사용한 경우
- java.io 패키지
3.2 Coffee Order APP
- 다양한 종류의 커피가 있다.
- 우유, 휘핑등 재료를 추가할 수 있다.
3.2.1 상속을 통한 설계
3.2.1.1 음료 인터페이스
더보기 접기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public abstract class Beverage {
// field
protected String description;
private boolean milk;
private boolean soy;
private boolean mocha;
private boolean whip;
// constructor
public Beverage() {
this .milk = false ;
this .soy = false ;
this .mocha = false ;
this .whip = false ;
}
abstract public void setDescription();
public String getDescription() {
if (hasMilk()) {description = description + " + milk" ;}
if (hasSoy()) {description = description + " + soy" ;}
if (hasMocha()) {description = description + " + mocha" ;}
if (hasWhip()) {description = description + " + whip" ;}
return description;
}
public double cost() {
double cost = 0 ;
if (hasMilk()) {cost = cost + 500 ;}
if (hasSoy()) {cost = cost + 500 ;}
if (hasMocha()) {cost = cost + 500 ;}
if (hasWhip()) {cost = cost + 500 ;}
return cost;
}
public void setMilk( boolean selection){ this .milk = selection;}
public boolean hasMilk() { return this .milk;}
public void setSoy( boolean selection) { this .soy = selection;}
public boolean hasSoy() { return this .soy;}
public void setMocha( boolean selection) { this .mocha = selection;}
public boolean hasMocha() { return this .mocha;}
public void setWhip( boolean selection) { this .whip = selection;}
public boolean hasWhip() { return this .whip;}
}
cs
접기
3.2.1.2 음료 interface 구현 클래스들
더보기 접기
- 다크 로스트 커피
public class DarkRoast extends Beverage{
@Override
public double cost() {
double darkRoast = 5000 ;
return darkRoast + super .cost();
}
@Override
public void setDescription() {
this .description = "다크 로스트" ;
}
}
cs
- 디카페인 커피
public class Decaf extends Beverage {
@Override
public double cost() {
double Decaf = 5000 ;
return Decaf + super .cost();
}
@Override
public void setDescription() {
this .description = "디카페인" ;
}
}
cs
- 에스프레소 커피
public class Espresso extends Beverage {
@Override
public double cost() {
double Espresso = 5000 ;
return Espresso + super .cost();
}
@Override
public void setDescription() {
this .description = "에스프레소" ;
}
}
cs
- 하우스 블렌드 커피
public class HouseBlend extends Beverage {
@Override
public double cost() {
double HouseBlend = 5000 ;
return HouseBlend + super .cost();
}
@Override
public void setDescription() {
this .description = "하우스 블렌드" ;
}
}
cs
- 홍차
public class RedTea extends Beverage {
@Override
public double cost() {
double Espresso = 5000 ;
return Espresso + super .cost();
}
@Override
public void setDescription() {
this .description = "홍차" ;
}
}
cs
접기
3.2.2 상속을 통한 설계한 Coffee Order App의 문제점
- 일부 Sub class에서는 적합하지 않은 기능을 Sub class 에 추가하게 되는 문제 (상속을 통해 상속받아선 안되는 기능까지 상속받는 문제)
홍차에는 휘핑 추가가 불가능한데 상속을 통해 디자인 했기 때문에 이러한 불가능한 것까지 가능하게 되어버린다.
(확장할 때 상속을 통해 확장하면 그 기능은 컴파일시에 확정되고, 모든 sub class에서 똑같은 행동을 상속받아야 한다)
- 첨가물(구성요소)의 가격이 달라질때 마다 super class의 cost() 메소드를 수정
- 첨가물(구성요소)의 종류가 많아지면 새로운 setter getter 메소드를 super class에 추가해야하고 cost() 메소드도 수정해야한다. ( 유연한 확장성 보장x)
3.2.3 유연한 확장을 위한 코드란?
super class를 건드리지 않고 확장하는 것이 중요하다.
기존 코드는 건드리지 않고 새로운 코드를 만들어서 새로운 기능을 추가할 수 있어야 한다.
(closed) (open)
3.2.4 디자인 원칙 - OCP(Open-Closed Principle)
- 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.
- 모든 부분에 OCP를 적용해서 디자인하기는 불가능 - 추상화의 복잡성, 여유의 부재
=> 가장 바뀔 확률이 높은 부분을 중점적으로 OCP적용
3.2.5 Decorator Pattern을 적용한 Coffee Order App
3.2.5.1 Component interface
감쌀 객체들의 타입임과 동시에 Decorator 객체들의 공통 타입이다.
더보기 접기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class Berverage {
protected String description;
public Berverage() {
this .description = "제목 없음." ;
}
public String getDescription() {
return description;
}
public abstract double cost();
}
cs
접기
3.2.5.2 ConcreteComponent 클래스들
Component interface를 구현하는 객체들
더보기 접기
- 다크 로스트 커피
public class DarkRoast extends Berverage {
public DarkRoast() {
this .description = "다크 로스트" ;
}
@Override
public double cost() {
return 3500 ;
}
}
cs
- 디카페인 커피
public class Decaf extends Berverage {
public Decaf() {
this .description = "디카페인" ;
}
@Override
public double cost() {
return 3000 ;
}
}
cs
- 에스프레소 커피
public class Espresso extends Berverage {
public Espresso() {
this .description = "에스프레소" ;
}
@Override
public double cost() {
return 2000 ;
}
}
cs
- 하우스 블렌드 커피
public class HouseBlend extends Berverage {
public HouseBlend() {
this .description = "하우스 블렌드" ;
}
@Override
public double cost() {
return 1500 ;
}
}
cs
접기
3.2.5.3 Decorator interface
decorator class들이 구현할 상위 interface.
이 interface는 Conponent interface를 구현함으로서 감싸질 객체와 Decorator 객체들의 형식을 같게 한다.
더보기 접기
import javax.annotation.Generated;
public abstract class CondimentDecorator extends Berverage {
@Override
public abstract String getDescription();
}
cs
접기
3.2.5.4 ConcreteDecorator 클래스들
Decorator interface를 구현하는 Decorator 클래스들이다.
더보기 접기
- 모카
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Mocha extends CondimentDecorator {
Berverage berverage;
public Mocha(Berverage berverage) {
this .berverage = berverage;
}
@Override
public String getDescription() {
return berverage.getDescription() + " + 모카" ;
}
@Override
public double cost() {
return berverage.cost() + 300 ;
}
}
cs
- 두유
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Soy extends Berverage {
Berverage berverage;
public Soy(Berverage berverage) {
this .berverage = berverage;
}
@Override
public String getDescription() {
return berverage.getDescription() + " + 두유" ;
}
@Override
public double cost() {
return berverage.cost() + 400 ;
}
}
cs
- 휘핑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Whip extends CondimentDecorator {
Berverage berverage;
public Whip(Berverage berverage) {
this .berverage = berverage;
}
@Override
public String getDescription() {
return berverage.getDescription() + " + 휘핑" ;
}
@Override
public double cost() {
return berverage.cost() + 400 ;
}
}
cs
접기
3.2.5.5 Test class
더보기 접기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class StarBucksCoffee {
public static void main( String args[]) {
System . out . println ( "첫번째 주문 : 에스프레스 주세요." );
Berverage berverage = new Espresso();
System . out . println (berverage.getDescription() + " 나왔습니다." );
System . out . println (berverage.cost() + "원 입니다.\n" );
System . out . println ( "두번째 주문 : 모카두번이랑 휘핑한번 추가해서 다크로스트 커피 주세요." );
Berverage berverage2 = new DarkRoast();
berverage2 = new Mocha(berverage2);
berverage2 = new Mocha(berverage2);
berverage2 = new Whip(berverage2);
System . out . println (berverage2.getDescription() + " 나왔습니다." );
System . out . println (berverage2.cost() + "원 입니다.\n" );
System . out . println ( "세번째 주문 : 두유,모카, 휘핑 추가해서 하우스블렌드 커피 주세요." );
Berverage berverage3 = new HouseBlend();
berverage3 = new Soy(berverage3);
berverage3 = new Mocha(berverage3);
berverage3 = new Whip(berverage3);
System . out . println (berverage3.getDescription() + " 나왔습니다." );
System . out . println (berverage3.cost() + "원 입니다.\n" );
}
}
cs
접기