데코레이터 패턴(Decorator pattern)
데코레이터 패턴은 객체의 기능을 동적으로 확장하기 위해 사용되는 디자인 패턴 중 하나입니다. 데코레이터 패턴은 기본 객체에 대해 런타임에 새로운 기능을 추가함으로써 객체의 행동을 유연하게 확장할 수 있습니다.
기본 개념
데코레이터 패턴은 객체를 감싸는 래퍼(Wrapper) 클래스를 사용하여 객체의 기능을 재정의합니다. 이렇게 되면 기본 객체를 참조하는 변수에 여러 개의 데코레이터 객체를 사용할 수 있으며, 각 데코레이터 객체는 자신의 기능을 추가할 수 있습니다.
데코레이터 패턴은 객체 간의 상속 관계를 통해 기능을 확장하는 것과 달리, 런타임에서 객체의 행동을 동적으로 확장하기 때문에 더욱 유연하고 확장성이 높습니다. 데코레이터 패턴을 사용하면 객체에 필요한 동작을 원하는대로 추가하거나 제거할 수 있습니다.
구성 요소
데코레이터 패턴은 기본 객체, 컴포넌트 인터페이스, 데코레이터 클래스로 구성됩니다.
기본 객체: 장식할 기본 객체를 나타냅니다. 데코레이터 패턴은 기본 객체에 대한 참조를 갖고 있어야 합니다.
컴포넌트 인터페이스: 데코레이터와 기본 객체가 구현해야 하는 메서드를 정의하는 인터페이스입니다. 이를 통해 데코레이터는 자신만의 기능을 확장하고 기본 객체의 메서드를 호출할 수 있습니다.
데코레이터 클래스: 컴포넌트 인터페이스를 구현하며, 기본 객체를 참조하고 자신의 기능을 추가하는 클래스입니다. 데코레이터 클래스는 기본 객체와 같은 인터페이스를 구현하여 클라이언트가 원하는 수만큼의 데코레이터를 연결할 수 있도록 합니다.
동작 방식
기본 객체 생성: 기본 객체를 생성합니다.
데코레이터 생성: 데코레이터 객체를 생성하며, 기본 객체를 참조합니다.
기본 객체 참조: 데코레이터 객체는 자신이 참조하는 기본 객체를 호출하고, 필요한 경우에는 추가적인 기능을 수행합니다.
데코레이터 체인 구성: 클라이언트는 데코레이터 객체의 체인을 구성하여 여러 개의 데코레이터를 적용할 수 있습니다. 이때 첫 번째 데코레이터는 기본 객체를 참조하고, 이후의 데코레이터는 이전 데코레이터를 참조합니다.
클라이언트 호출: 클라이언트는 데코레이터 체인에 있는 첫 번째 데코레이터를 호출하여 결과를 얻습니다. 이때 체인을 따라가며 각 데코레이터에서 필요한 작업을 수행한 뒤, 마지막으로 기본 객체를 호출하여 최종 결과를 얻습니다.
장점
객체 간의 상속을 사용하지 않고도 기능을 유연하게 확장할 수 있습니다.
런타임에 동적으로 기능을 추가하거나 제거할 수 있습니다.
유연하고 확장성이 높은 구조를 갖게 됩니다.
예시
가령, 커피 주문 시스템을 구현한다고 가정해봅시다. 커피에는 기본적인 특징(온도, 시럽 추가 여부, 휘핑 크림 추가 여부 등)이 있지만, 고객은 이 외에도 개인적인 요구사항을 추가할 수 있어야 합니다. 이때 데코레이터 패턴을 사용하면 기본 커피 객체에 원하는 만큼의 데코레이터를 추가하여 고객의 요구에 맞는 커피를 제공할 수 있습니다.
public interface Coffee {
String getDescription();
double getCost();
}
public class BasicCoffee implements Coffee {
@Override
public String getDescription() {
return "Basic Coffee";
}
@Override
public double getCost() {
return 2.0;
}
}
public abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public double getCost() {
return coffee.getCost();
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + " with sugar";
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
}
public class WhipCreamDecorator extends CoffeeDecorator {
public WhipCreamDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + " with whipped cream";
}
@Override
public double getCost() {
return super.getCost() + 1.0;
}
}
public class Client {
public static void main(String[] args) {
Coffee basicCoffee = new BasicCoffee();
Coffee sugarCoffee = new SugarDecorator(basicCoffee);
Coffee whippedCreamCoffee = new WhipCreamDecorator(sugarCoffee);
System.out.println(whippedCreamCoffee.getDescription());
System.out.println(whippedCreamCoffee.getCost());
}
}
위 예시에서 기본 커피 객체인 BasicCoffee
를 생성한 뒤, SugarDecorator
와 WhipCreamDecorator
를 차례대로 추가하여 최종 커피를 생성했습니다. 이렇게 함으로써 고객은 원하는 만큼의 데코레이터를 이용해 본인에게 맞는 커피를 주문할 수 있게 되었습니다.
결론
데코레이터 패턴은 객체의 기능을 동적으로 확장하기 위한 강력한 디자인 패턴입니다. 객체 간의 상속을 이용하지 않고도 유연하고 확장성이 높은 구조를 갖출 수 있으며, 런타임에 동적으로 기능을 추가하거나 제거할 수 있습니다. 이를 통해 객체 지향 프로그래밍의 핵심 원칙 중 하나인 개방-폐쇄 원칙(OCP)을 준수할 수 있습니다.
댓글