데코레이터 (Decorator) 패턴
데코레이터 패턴이란, 이름에서 알 수 있듯이 기존 객체에 새로운 요소를 추가할 때 쓰입니다.
흔히 음식이나 방을 꾸밀 때 데코한다고 하죠?
데코레이터 패턴을 알게되면 이제는 클래스에도 데코를 할 수 있게 될 겁니다.
방을 데코할땐 원래 있던 가구를 치우거나 바꿀수도 있습니다.
하지만 데코레이터 패턴을 사용해 클래스를 바꿀 때에는
기존의 클래스를 수정하지 않으면서, 새로운 기능을 추가할 때 사용합니다.
정확히는, 기존의 클래스를 감싸는(wrap) 방식으로 이루어집니다.
이제 음료를 데코레이터 해봅시다.
카페를 차렸습니다.
Uknow 커피 디자인 패턴 마을점. 신규 오픈하였습니다.
아직 메뉴가 몇개 없지만, 이를 전산화하기 위한 작업을 해봤습니다.
클래스 Boooooom!!!
Baverage 추상 클래스
public abstract class Beverage {
String description = "제목 없음";
public String getDescription() {
return description;
}
public abstract int cost();
}
-----------------------------------------
public class CafeLatte extends Beverage {
public CafeLatte() {
description = "카페라떼";
}
public int cost() {
return 3500;
}
}
-----------------------------------------
public class Americano extends Beverage {
public Americano() {
description = "아메리카노";
}
public int cost() {
return 3000;
}
}
------------------------------------------
(이하 생략)
Beverage는 에소프레소, 아메리카노, 카페라떼, 유자차의 바탕이 될 추상 클래스입니다.
이제 여기에 토핑 (휘핑, 샷 추가, 자바칩)을 추가한 클래스도 만들어보겠습니다.
public class AmericanoExtraShot extends Beverage {
@Override
public String getDescription() {
return "Americano, Extra Shot";
}
@Override
public int cost() {
return 3000;
}
}
-------------------------------------------------------
public class AmericanoJavaChip extends Beverage {
@Override
public String getDescription() {
return "Americano, Java Chip";
}
@Override
public int cost() {
return 3500;
}
}
-----------------------------------------------------------
.
.
.
(이하 15개 클래스)
💥 클래스가 그냥 엄청 많아졌습니다 ㅇㅁㅇ!!! 💥
음료와 토핑까지 모두 표현하려 하니, 클래스의 개수가 폭발해버렸습니다...
하지만, 좀 더 유연하게 생각을 하여, 토핑(Topping)인 만큼, 기존에 있는 음료에 추가(데코)한다고 생각하면 어떨까요?
기존에 있는 음료를 토핑으로 감싸서 말이죠.
기존에 있는 것에 새롭게 추가(데코)하자.
public abstract class Beverage {
String description = "제목 없음";
public String getDescription() {
return description;
}
public abstract int cost();
}
-------------------------------------------
public class CafeLatte extends Beverage {
public CafeLatte() {
description = "카페라떼";
}
public int cost() {
return 3500;
}
}
-------------------------------------------
public abstract class ToppingDecorator extends Beverage {
Beverage beverage;
public abstract String getDescription();
}
-------------------------------------------
public class ExtraShot extends ToppingDecorator {
public ExtraShot(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", 샷 추가";
}
public int cost() {
return beverage.cost() + 500;
}
}
-------------------------------------------
public class Whip extends ToppingDecorator {
public Whip(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", 휘핑";
}
public int cost() {
return beverage.cost() + 600;
}
}
-------------------------------------------
(이하 생략)
카페라떼는 Beverage를 상속받아, 음료의 가격을 계산하는 cost()를 가지고 있습니다.
샷추가, 휘핑 추가는 데코레이터 패턴으로써,
둘 역시 Beverage를 상속받은 ToppingDecorator를 상속받았으므로 cost() 메소드가 존재합니다.
또한, 샷, 휘핑 등은 Beverage를 상속받음으로써 샷추가, 휘핑 추가를 감싸고 있는 형태 역시 Beverage 객체로 생각할 수 있습니다.
결국 Beverage의 자식 클래스이니까요.
이제, 이걸 토대로 한 번 주문을 넣어볼까요?
public class Main {
public static void main(String[] args) {
Beverage 에소프레소 = new Espresso();
System.out.println(에소프레소.getDescription() + " " + 에소프레소.cost() + "원");
Beverage 아메리카노 = new Americano();
아메리카노 = new ExtraShot(아메리카노); // 아메리카노 샷 추가!
아메리카노 = new ExtraShot(아메리카노); // 아메리카노 또 샷 추가! 더블 샷!
System.out.println(아메리카노.getDescription() + " " + 아메리카노.cost() + "원");
Beverage 카페라떼 = new CafeLatte();
카페라떼 = new Whip(카페라떼); // 카페라떼에 휘핑 추가!
카페라떼 = new JavaChip(카페라떼); // 카페라떼에 자바칩 추가!
System.out.println(카페라떼.getDescription() + " " + 카페라떼.cost() + "원");
Beverage 유자차 = new CitronTee();
유자차 = new Whip(유자차); // 유자차에 휘핑 추가!...???
System.out.println(유자차.getDescription() + " " + 유자차.cost() + "원");
}
}
// 출력
에스프레소 2500원
아메리카노, 샷 추가, 샷 추가 4000원
카페라떼, 휘핑, 자바칩 4800원
유자차, 휘핑 2600원
이제 우리는 핵심 음료와 토핑만 가지고, 여러 결과물들을 만들어낼 수 있게 되었습니다.
아메리카노 더블 샷, 카페라떼 휘핑 추가, 유자차 휘핑 등등.
최종 클래스의 cost() 메서드를 수행할 경우,
해당 클래스는 감싸고 있는 안쪽 클래스의 cost()를 호출하고, 리턴값에 자신의 금액을 더합니다.
즉, 카페라떼 + 샷 + 휘핑의 경우,
휘핑 클래스의 cost() 호출 -> 샷 추가 클래스의 cost() 호출 -> 카페라떼 클래스의 cost() 호출
-> (카페라떼) 3500 반환 -> 반환된 3500 + 샷 추가(500) -> 반환된 4000 + 휘핑 (600) -> 최종적으로 4600 리턴하는 구조지요.
자신이 해야할 일만 진행하고, 자신이 장식하고 있는 객체에게 어떤 행위를 위임하는 방식으로 진행됩니다.
자바 I/O
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
자바의 I/O 역시 데코레이터 패턴을 기반으로 만들어졌습니다.
InputStream을 여러 데코레이터로 감싸서 추가 기능을 더하고 있지요.
데코레이터 패턴의 특징
- 자신의 작업을 수행하며, 자신이 장식하고 있는 객체에게 행동을 위임할 수 있다
- 기존의 클래스를 납둔 채 추가 기능을 확장할 수 있다 (OCP)
- 한 객체를 여러 데코레이터로 감쌀 수 있다
- 데코레이터의 슈퍼 클래스는 자신이 장식하고 있는 객체의 슈퍼 클래스와 같다
- 런타임 환경에서 동적으로 기능을 추가/변경하는 것이 가능하다
후기
디자인 패턴을 공부할수록, 라이브러리/프레임워크가 어떻게 설계되었는지 조금씩 알아가는 느낌입니다.
해당 포스팅의 자세한 코드는 깃허브에서 볼 수 있습니다.
https://github.com/yoon6763/design-pattern
GitHub - yoon6763/design-pattern: 재밌지만 심오한 디자인 패턴의 세계.
재밌지만 심오한 디자인 패턴의 세계. Contribute to yoon6763/design-pattern development by creating an account on GitHub.
github.com
본 포스팅은 헤드퍼스트의 디자인패턴을 바탕으로 작성되었습니다.
'CS 지식 > 디자인 패턴' 카테고리의 다른 글
[디자인 패턴] Adaptor(어댑터) 패턴 (1) | 2024.05.08 |
---|---|
[디자인 패턴] Singleton(싱글톤) 패턴 (1) | 2024.03.16 |
[디자인 패턴] Factory(팩토리) 패턴 (0) | 2024.03.10 |
[디자인 패턴] Observer(옵저버) 패턴 (0) | 2023.09.08 |
[디자인 패턴] Strategy(전략) 패턴 + Template Callback Pattern (0) | 2023.06.20 |