데코레이터(Decorator) 패턴은 객체의 기능을 동적으로 확장할 수 있는 패턴입니다. 이 패턴은 객체를 감싸는 데코레이터 클래스를 통해 기능을 추가하거나 수정할 수 있으며, 원본 객체의 인터페이스를 유지합니다. 자바 코드를 통해 데코레이터 패턴의 예시를 살펴보겠습니다.
가정해보겠습니다. 우리는 커피(Coffee)를 만들고 있으며, 다양한 종류의 커피에 여러 가지 토핑(Topping)을 추가할 수 있습니다. 데코레이터 패턴을 사용하여 커피에 토핑을 추가하는 예시를 만들어보겠습니다.
먼저, 커피를 나타내는 Coffee 인터페이스를 정의합니다:
public interface Coffee {
String getDescription();
double getCost();
}
다음으로, 커피의 구체적인 종류를 구현하는 클래스인 SimpleCoffee를 만듭니다:
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 1.0;
}
}
SimpleCoffee 클래스는 Coffee 인터페이스를 구현하며, getDescription() 메서드와 getCost() 메서드를 구현하여 각각 커피의 설명과 가격을 반환합니다.
이제 데코레이터 클래스인 CoffeeDecorator를 만들어 커피에 토핑을 추가할 수 있게 해봅시다
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee decoratedCoffee) {
this.decoratedCoffee = decoratedCoffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
}
CoffeeDecorator 클래스는 Coffee 인터페이스를 구현하며, 내부에 다른 Coffee 객체를 갖고 있습니다. getDescription() 메서드와 getCost() 메서드는 내부의 Coffee 객체에게 위임하여 호출합니다.
마지막으로, 토핑을 나타내는 구체적인 데코레이터 클래스를 만듭니다. 예를 들어, MilkDecorator와 SugarDecorator를 구현해보겠습니다:
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Milk";
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public String getDescription() {
return super.getDescription() + ",Sugar";
}
@Override
public double getCost() {
return super.getCost() + 0.3;
}
}
MilkDecorator와 SugarDecorator는 CoffeeDecorator를 상속받으며, 각각 getDescription() 메서드와 getCost() 메서드를 오버라이딩하여 토핑을 추가한 설명과 가격을 반환합니다.
이제 데코레이터 패턴을 사용하여 커피에 토핑을 추가하는 예시 코드를 작성합니다:
public class Main {
public static void main(String[] args) {
// 원본 커피 생성
Coffee simpleCoffee = new SimpleCoffee();
System.out.println("Description: " + simpleCoffee.getDescription());
System.out.println("Cost: $" + simpleCoffee.getCost());
System.out.println("-----------------------");
// 토핑 추가한 커피 생성
Coffee coffeeWithMilk = new MilkDecorator(simpleCoffee);
System.out.println("Description: " + coffeeWithMilk.getDescription());
System.out.println("Cost: $" + coffeeWithMilk.getCost());
Coffee coffeeWithMilkAndSugar = new SugarDecorator(coffeeWithMilk);
System.out.println("Description: " + coffeeWithMilkAndSugar.getDescription());
System.out.println("Cost: $" + coffeeWithMilkAndSugar.getCost());
}
}
위 예시에서는 SimpleCoffee 객체를 생성하여 원본 커피를 만들고, MilkDecorator와 SugarDecorator를 사용하여 토핑을 추가한 커피를 만듭니다. getDescription() 메서드와 getCost() 메서드를 호출하여 커피의 설명과 가격을 출력합니다.
실행 결과는 다음과 같습니다:
Description: Simple Coffee
Cost: $1.0
-----------------------
Description: Simple Coffee, Milk
Cost: $1.5
Description: Simple Coffee, Milk, Sugar
Cost: $1.8
이 예시에서는 데코레이터 패턴을 사용하여 커피에 토핑을 추가하였습니다. CoffeeDecorator 클래스를 상속받는 구체적인 데코레이터 클래스를 사용하여 커피의 기능을 동적으로 확장할 수 있으며, 원본 커피의 인터페이스를 유지합니다.