데코레이터 패턴
데코레이터 패턴은 객체를 동적으로 기능을 확장하거나 추가할 수 있는 패턴입니다. 특징으로는 객체를 감싸는 방식으로 기능을 추가하는건데요. 말로 설명하는 것보다 예제를 통해 배워보도록 하겠습니다.
예제
커피를 예로 들어보겠습니다.
커피의 종류는 아메리카노, 카페라떼, 등등이 있습니다.
이 커피들에 설탕이나 샷을 추가할 수 있다고 했을때, 어떻게 구현해야 할까요?
커피와 추가메뉴를 나누고 거기에서 커피객체에 메뉴를 추가하는 식으로 구현할 수 있을것입니다. 그러나 오늘은 데코레이터 패턴을 사용해상속을 통한 확장이 아닌 객체를 감싸는 방식으로 확장을 해보겠습니다.
전체적인 구조는 다음과 같습니다.
Coffee
public interface Coffee {
String getDescription();
int getCost();
}
public class Americano implements Coffee {
@Override
public String getDescription() {
return "아메리카노";
}
@Override
public int getCost() {
return 5000;
}
}
Coffee에는 메뉴의 이름을 반환하는 getDescription가 가격을 반환하는 getCost()가 있습니다.
Coffee를 상속받은 Americano를 구현했습니다.
CoffeeDecorator
public abstract class CoffeeDecorator implements Coffee {
private final Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public int getCost() {
return coffee.getCost();
}
}
public class ShotDecorator extends CoffeeDecorator {
public ShotDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", 샷 추가";
}
@Override
public int getCost() {
return super.getCost() + 500;
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", 설탕 추가";
}
@Override
public int getCost() {
return super.getCost() + 300;
}
}
CoffeeDecorator를 상속받아 커피에 추가할 수 있는 옵션인 설탕과 샷 객체를 구현했습니다.
CoffeeDecorator의 구현객체를 보면 멤버변수에 Coffee를 가지고있습니다. 추가옵션은 Coffee가 반드시 필요하기 때문입니다.
실행결과
public static void main(String[] args) {
Coffee coffee = new SugarDecorator(new ShotDecorator(new Americano()));
System.out.println(coffee.getDescription());
System.out.println(coffee.getCost());
// 아메리카노, 샷 추가, 설탕 추가
// 5800
}
coffee 변수안에 Decorator를 사용해서 추가옵션을 넣어 줄 수 있었습니다.
이렇게 하면 기존코드를 수정하거나 변경없이도 추가 확장할 수 있습니다.
여기서 살짝 아쉬운부분은 CoffeeDecorator에서 Coffee를 이미 Override를 했기때문에 Decorator를 구현할때 Override를 강제하기 않는다는것이었는데요. 그래서 강제할 수 있도록 약간 더 수정해보았습니다.
public abstract class CoffeeDecorator implements Coffee {
private final Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription() + getExtra();
}
@Override
public int getCost() {
return coffee.getCost() + getExtraCost();
}
abstract String getExtra();
abstract int getExtraCost();
}
public class ShotDecorator extends CoffeeDecorator {
public ShotDecorator(Coffee coffee) {
super(coffee);
}
@Override
String getExtra() {
return ", 샷 추가";
}
@Override
int getExtraCost() {
return 500;
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
String getExtra() {
return ", 설탕 추가";
}
@Override
int getExtraCost() {
return 300;
}
}
CoffeeDecorator에서 getExtra()와 getExtraCost()를 추상화하여 상속받은 클래스에게 구현을 강제하게 했습니다.
이렇게 하니까 더 코드가 깔끔해진것 같습니다.
이 데코레이터 패턴은 유연하게 확장이 가능하고, 책임을 분리하고, DIP를 준수하는 등 좋은 장점들이 많으나 코드 복잡성이 증가하고, 순서에 의존하는 단점도 존재합니다.
'디자인패턴 > 구조' 카테고리의 다른 글
자바(JAVA) - 경량 패턴(Flyweight Pattern) (0) | 2024.12.05 |
---|---|
자바(JAVA) - 복합 패턴(Composite Pattern) (0) | 2024.12.05 |
자바[JAVA] - 퍼사드 패턴(Facade Pattern) (0) | 2024.11.09 |
자바(JAVA) - 브릿지 패턴(Bridge Pattern) (0) | 2024.09.30 |
자바(JAVA) - 어댑터 패턴(Adapter Pattern) (0) | 2024.09.24 |