데코레이터(Decorator): 구조패턴
어떤 객체에 책임(기능)을 동적으로 추가하는 패턴, 기본 기능을 가지고 있는 클래스를 하나 만들고, 상속과 합성을 이용하여 추가할 수 있는 기능들을 추가하기 편하도록 하는 설계
💡 프록시 란? 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아주는 것, 대리자와 대리인 같은 역할을 한다. 타깃과 같은 인터페이스를 구현하며, 프록시가 타깃을 제어할 수 있다. 접근을 제어하거나 부가기능을 추가할 수 있다.
패턴의 이름처럼 객체에 장식을 얹듯 다른 기능까지 추가하는 패턴을 말한다. 여기서 동적으로 추가할 때는 보통 특정 객체를 결합하는 방식을 사용한다.
데코레이터 패턴은 타깃에 부가적인 기능을 런타임 시 다이나믹하게 부여해주기 위해 프록시를 사용하는 패턴이다.
다이나믹하게 부여해준다는 의미는 컴파일 시점, 즉 코드 상에서는 어떤 방법과 순서로 프록시와 타깃이 연결되어 사용되는지 정해져 있지 않다는 뜻이다.
그림에서 알 수 있는 듯이 데코레이터 패턴에서는 프록시가 꼭 한개로 제한되지 않는다. 직접 타깃을 사용하도록 고정시킬 필요도 없다. 이를 위해 데코레이터 패턴에서는 같은 인터페이스를 구현한 타겟과 여러 개의 프록시를 사용할 수 있다.
데코레이터 패턴은 인터페이스로 접근하기 때문에 자신이 최종 타깃을 위임하는지, 아니면 다음 단계의 데코레이터 프록시로 위임하는지 알지 못한다 → 캡슐화!
그렇기 때문에 다음 위임 대상은 인터페이스로 선언하고 생성자를 통해 외부에서 의존을 주입받을 수 있도록 해야한다.
Component : 실질적인 인스턴스를 컨트롤하는 역할, 공통 기능을 정의한다. 클라이언트는 Component를 통해 실제 객체를 사용한다.
ConcreteComponent : Component의 실질적인 인스턴스의 부분으로 책임의 주체의 역할, 기본기능을 구현한다.
Decorator : Component와 ConcreteDecorator를 동일시 하도록 해주는 역할, 많은 수가 존재하는 Decorator의 공통기능을 제공한다.
ConcreteDecoreator : 실질적인 장식 인스턴스 및 정의이며 추가된 책임의 주체, 기본 기능에 추가되는 개별적인 기능을 뜻한다. ConcreteDecorator 클래스는 ConcreteComponent 객체에 대한 참조가 필요하다. 이는 Decorator 클래스에서 Component 클래스로의 ‘합성(composition) 관계’를 통해 표현한다.
데코레이터 패턴의 장단점
장점:
- 기존 코드를 수정하지 않고도 데코레이터 패턴을 통해 행동을 확장시킬 수 있다.
- 구성과 위임을 통해서 실행중에 새로운 행동을 추가할 수 있다.
단점:
- 의미없는 객체들이 너무 많이 추가될 수 있다.
- 데코레이터를 너무 많이 사용하면 코드가 필요 이상으로 복잡해질 수 있다.
이런 경우에 사용하면 좋다.
- 클래스의 요소들을 계속해서 수정을 하면서 사용하는 구조가 필요한 경우
- 여러 요소들을 조합해서 사용하는 클래스 구조인 경우
자바에서의 데코레이터 패턴
자바 IO 패키지의 InputStream과 OutputStream 구현 클래스는 데코레이터 패턴이 사용된 대표적이 예이다.
InputStream is = new BufferedInputStream(new FileInputStream("test.txt"));
// 데이터를 읽는다. // 바이트 단위로 읽어오는 버퍼 스트림 // 파일 내용 읽어오기
InputStream이라는 인터페이스를 구현한 타깃인 FileInputStream에 버퍼 읽기 기능을 제공해주는 BufferedInputStream이라는 데코레이터를 적용한 예이다.
BufferedReader br;
br = new BufferedReader(new FileReader("sample.txt"));
br = new BufferedReader(new InputStreamReader(System.in));
바이트스트림에서 문자스트림으로 이어주는 InputStreamReader객체를 생성하여, System.in 객체(키보드 입력) 을 인수값으로 주는 부분을 통해, 문자스트림인 BufferedReader가 바이트스트림인 System.in객체를 읽어들일 수 있도록 하는 데코레이터 패턴이 실행된다.
데코레이터 패턴 예제
카페를 예제로 들어보겠다. 카페에서 파는 음료는 최소 10개는 될 것이다. 그 뿐 아니라, 손님의 입맛에 따라 샷을 더 추가할 수도, 크림을 더 추가할 수도, 우유를 더 추가할 수도 있다. 이럴 때 사용가능한 것이 데코레이터 패턴! 기존 기능에 부가적인 기능을 추가해주면 된다.
Component
public interface Beverage {
public int cost();
public String getDescription();
}
ConcreteComponent
public class Espresso implements Beverage{
@Override
public int cost() {
return 2000;
}
@Override
public String getDescription() {
return "에스프레소";
}
}
Decorator
public class Decorator implements Beverage{
private Beverage beverage;
public Decorator(Beverage beverage){
this.beverage = beverage;
}
@Override
public int cost() {
return beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription();
}
}
ConcreteDecorator
public class Milk extends Decorator{
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public int cost(){
return super.cost() + 1000;
}
@Override
public String getDescription(){
return super.getDescription() + "+ 우유";
}
}
ConcreateDecorator
public class Water extends Decorator{
public Water(Beverage beverage) {
super(beverage);
}
@Override
public int cost(){
return super.cost() + 500;
}
@Override
public String getDescription(){
return super.getDescription() + "+ 물";
}
}
Client
public class CoffeeShop {
public static void main(String[] args) throws FileNotFoundException {
Beverage americano = new Water(new Espresso());
System.out.println("아메리카노의 가격은: " + americano.cost() + " 구성: " + americano.getDescription());
Beverage cafelatte = new Milk(new Espresso());
System.out.println("카페라떼의 가격은: " + cafelatte.cost() + " 구성: " + cafelatte.getDescription());
Beverage customCoffee = new Espresso();
customCoffee = new Water(new Milk(new Espresso()));
System.out.println("커스텀 커피 가격은: " + customCoffee.cost() + " 구성: " + customCoffee.getDescription());
}
}
데코레이터 패턴 예제2
//트랜잭션기능이 적용된 데코레이터 패턴
//UserServiceImpl 클래스가 UserService를 구현한 상태
public class UserServiceTx implements UserService{
UserService userService;
PlatformTransactionManger transactionManger;
public UserServiceTx(UserService userService, PlatformTransactionManger transactionManger){
this.userService = userService;
this.transactionManger = transactionManger;
}
publid void add(User user){
this.userService.add(user);
}
public void upgradeLevels(){
TransactionStatus status = this.transactionManger.getTransaction(new DefaultTransactionDefinition());
try{
userService.upgradeLevels();
this.transactionManger.commit(status); // 데코레이터
}catch(RuntimeException e){
this.transactionManager.rollback(status);
throw e;
}
}
}
Reference
https://coding-factory.tistory.com/713
토비의 스프링 - 데코레이터 패턴
'OOP > Design Pattern' 카테고리의 다른 글
11. 플라이웨이트(Flyweight) 패턴 (0) | 2023.02.20 |
---|---|
10. 퍼사드(Facade) 패턴 (0) | 2023.02.20 |
08. 컴포지트(Composite) 패턴 (0) | 2023.02.14 |
07. 어댑터(Adapter) 패턴 (0) | 2023.02.14 |
06. 싱글톤(Singleton)패턴 (0) | 2023.02.13 |