데코레이터 패턴은 기존 뼈대(클래스)는 유지하되, 이후 필요한 형태로 꾸밀 때 사용합니다.
예를 들어 서로 다른 인터페이스를 지닌 2개의 객체를 묶어 단일 인터페이스를 제공하거나 객체들을 서로 묶어 새로운 기능을 제공하는 패턴입니다.
확장이 필요한 경우 상속의 대안으로도 활용하며, SOLID 중에서 개방폐쇄 원칙(OCP)과 의존역전원칙(DIP)를 따릅니다.
- ConcreteComponent 과 Decorator 가 구현할 인터페이스이며, 두 클래스의 공통 기능을 담고 있습니다.
- 사용자가 데코레이터 패턴은 사용해서 인스트턴스를 생성할 때 Component(인터페이스) 를 사용합니다.
ConcreteComponent
- 기본 기능을 구현할 클래스입니다.
- Decorator를 통해 기능을 확장할 수 있는 뼈대 클래스가 됩니다.
- 해당 클래스의 변경 없이 Decorator 클래스를 통해서 기능이 확장되는 것입니다.
Decorator
- Decorate 할 추상 클래스입니다.
- 해당 Decorator 클래스로 직접적으로 인스턴스를 생성하지 않으며, 다른 하위 객체에 상속되어 ConcreteComponent의 기능을 전달, 확장합니다.
- ConcreteComponent 클래스와 동등한 관계를 가지며, ConcreteComponent 를 인자로 사용하여 ConcreteComponent 의 변형없이 제 3의 클래스(ConcreteDecorator)에 ConcreteComponent 기능을 확장해서 사용합니다.
ConcreteDecorator
- Decorator 추상 클래스를 상속받아 ConcreteComponent의 기능을 확장하여 구현합니다.
- 데코레이터 패턴의 결과물이라고 할 수 있습니다.
데코레이터 패턴 구현 예시 코드
// ICar.java
public interface ICar {
int getPrice();
void showPrice();
}
우선 Component 에 해당 되는 인터페이스를 생성합니다.
위의 인터페이스 ICar는 데코레이터 패턴에서, 기존의 핵심 로직인 ConcreteComponent 의 인스턴스와 데코레이터 결과물인 ConcreteDecorator 의 인스턴스를 생성하는데 사용합니다.
// Audi.java
public class Audi implements ICar {
private int price;
public Audi(int price) {
this.price = price;
}
@Override
public int getPrice() {
return this.price;
}
@Override
public void showPrice() {
System.out.println("audi의 가격은 "+this.price+"원 입니다.");
}
}
우선 ICar 인터페이스를 상속받는 Audi 클래스(ConcreteComponent)를 생성합니다.
Audi 클래스의 변형없이 데코레이터 패턴을 활용하여 확장을 합니다.
확장을 위한 데코레이터 클래스는 아래의 AudiDecorator 클래스입니다.
// AudiDecorator.java
public class AudiDecorator implements ICar {
protected ICar audi;
protected String modelName;
protected int modelPrice;
public AudiDecorator(ICar audi, String modelName, int modelPrice) {
this.audi = audi;
this.modelName = modelName;
this.modelPrice = modelPrice;
}
@Override
public int getPrice() {
return audi.getPrice() + modelPrice;
}
@Override
public void showPrice() {
System.out.println(modelName + "의 가격은 "+getPrice()+"원 입니다.");
}
}
Audi 클래스의 부모 인터페이스인 ICar를 상속받아 AudiDecorator 클래스를 만들고 ICar를 인자로 받아 생성자를 만듭니다.
ICar 인터페이스를 상속받아 메소드를 오버라이드합니다.
이때 오버라이드를 통해 기능이 확장되는 것입니다.
// A3.java
public class A3 extends AudiDecorator {
public A3(ICar audi, String modelName) {
super(audi, modelName, 1000);
}
}
// A4.java
public class A4 extends AudiDecorator {
public A4(ICar audi, String modelName) {
super(audi, modelName, 2000);
}
}
확장을 구현할 클래스를 만들고 AudiDecorator 클래스를 상속하여 생성자를 만듭니다.
public class Main {
public static void main(String[] args) {
ICar audi = new Audi(1000);
audi.showPrice();
ICar audi3 = new A3(audi, "A3");
audi3.showPrice();
ICar audi4 = new A4(audi, "A4");
audi4.showPrice();
}
}
즉, A3, A4 클래스 인스턴스를 생성하게 되면 Audi(ConcreteComponent)의 기능을 확장해서 사용할 수 있게 되는 것입니다.
여기까지가 데코레이션 패턴에 해당되는 예시였습니다.
그럼 데코레이션 패턴을 통해 A3, A4 클래스를 만드는 것과 일반 상속을 통해 A3, A4 클래스를 만드는 것이 무슨 차이가 가 있을까요?
상속을 통한 기능의 확장은 각 기능별로 클래스를 추가해야 합니다.
반면, 데코레이션 패턴에 의한 기능의 확장은 데코레이션 클래스를 통해 A3, A4 클래스를 일괄적으로 확장할 수 있습니다.
즉, 기능 확장으로 개별 클래스를 여러 개를 만들어야할 상황에서 데코레이션 패턴을 이용하는 것이 코드 절약을 할 수 있습니다.
'Java' 카테고리의 다른 글
java - 파사드 패턴(구조 패턴) (0) | 2021.08.24 |
---|---|
java - 옵저버 패턴(행위 패턴) (0) | 2021.08.23 |
java - 프록시 패턴(구조 패턴) (0) | 2021.08.23 |
java - 어댑터 패턴(구조 패턴) (0) | 2021.08.20 |
java - 싱글톤 패턴(생성 패턴) (0) | 2021.08.20 |
댓글