데코레이터 패턴은 개체에 동적으로 새로운 기능을 추가할 수 있게 해주는 디자인 패턴이다. 개체를 다양한 방법으로 확장할 수 있어 코드의 유연성과 재사용성을 높일 수 있으며, 기존 개체를 변경하지 않고 기능을 확장할 수 있다.
예를 들어 커피머신을 생각해 보자
커피머신의 기본은 당연히 커피이다. 하지만 우유나 설탕을 추가함으로써 여러 조합을 만들 수 있다.
Component 인터페이스는 커피의 공통 인터페이스 역할을 한다.
// Coffee 인터페이스: 커피의 공통 기능 정의
interface Coffee {
String getDescription();
double getCost();
}
ConcreateComponent 클래스
// BasicCoffee 클래스: 기본 커피
class BasicCoffee implements Coffee {
@Override
public String getDescription() {
return "Basic Coffee";
}
@Override
public double getCost() {
return 2.0;
}
}
데코레이터 클래스에는 커피에 추가할 다른 재료들의 공통 클래스로 선언한다.
// CoffeeDecorator 클래스: 커피 데코레이터
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();
}
}
ConcreateDecorator 클래스에는 구체적인 데코레이터, 즉 우유와 설탕등을 선언한다.
// MilkDecorator 클래스: 우유를 추가하는 데코레이터
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
@Override
public double getCost() {
return coffee.getCost() + 0.5;
}
}
// SugarDecorator 클래스: 설탕을 추가하는 데코레이터
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
@Override
public double getCost() {
return coffee.getCost() + 0.2;
}
}
이를 사용하는 소스코드를 확인해 보자
// 사용 예시
public class DecoratorPatternDemo {
public static void main(String[] args) {
Coffee basicCoffee = new BasicCoffee();
System.out.println(basicCoffee.getDescription() + " $" + basicCoffee.getCost());
Coffee milkCoffee = new MilkDecorator(basicCoffee);
System.out.println(milkCoffee.getDescription() + " $" + milkCoffee.getCost());
Coffee milkAndSugarCoffee = new SugarDecorator(milkCoffee);
System.out.println(milkAndSugarCoffee.getDescription() + " $" + milkAndSugarCoffee.getCost());
}
}
//출력
//Basic Coffee $2.0
//Basic Coffee, Milk $2.5
//Basic Coffee, Milk, Sugar $2.7
실무에서는 어떻게 자주 쓰일까?
웹 애플리케이션에서 Request를 처리하기 전에 여러 가지 기능을 추가하는 미들웨어를 구현해야 하는 경우를 생각해 보자.
기본적인 요청 처리기를 선언한다.
// RequestHandler 인터페이스: 요청 처리의 공통 인터페이스
interface RequestHandler {
void handleRequest(String request);
}
// 기본 요청 처리기
class BasicRequestHandler implements RequestHandler {
@Override
public void handleRequest(String request) {
System.out.println("Handling request: " + request);
}
}
데코레이터 클래스를 선언한다.
// RequestHandler 데코레이터: 요청 처리기에 기능을 추가하는 공통 클래스
abstract class RequestHandlerDecorator implements RequestHandler {
protected RequestHandler decoratedHandler;
public RequestHandlerDecorator(RequestHandler decoratedHandler) {
this.decoratedHandler = decoratedHandler;
}
@Override
public void handleRequest(String request) {
decoratedHandler.handleRequest(request);
}
}
로깅과 인증을 처리하는 데코레이터 클래스를 선언한다.
// LoggingDecorator 클래스: 요청을 처리하기 전에 로깅을 추가
class LoggingDecorator extends RequestHandlerDecorator {
public LoggingDecorator(RequestHandler decoratedHandler) {
super(decoratedHandler);
}
@Override
public void handleRequest(String request) {
System.out.println("Logging request: " + request);
decoratedHandler.handleRequest(request);
}
}
// AuthenticationDecorator 클래스: 요청을 처리하기 전에 인증을 추가
class AuthenticationDecorator extends RequestHandlerDecorator {
public AuthenticationDecorator(RequestHandler decoratedHandler) {
super(decoratedHandler);
}
@Override
public void handleRequest(String request) {
System.out.println("Authenticating request: " + request);
// 인증 로직 추가
decoratedHandler.handleRequest(request);
}
}
// RedisCachingDecorator 클래스: 요청을 처리하기 전에 Redis 캐싱을 추가
class RedisCachingDecorator extends RequestHandlerDecorator {
public RedisCachingDecorator(RequestHandler decoratedHandler) {
super(decoratedHandler);
}
@Override
public void handleRequest(String request) {
if (isCached(request)) {
System.out.println("Fetching from cache: " + request);
} else {
System.out.println("Caching request: " + request);
decoratedHandler.handleRequest(request);
cacheRequest(request);
}
}
private boolean isCached(String request) {
// Redis 캐시에서 요청을 조회하는 로직 (가상의 예제)
return false; // 실제 구현에서는 Redis에서 요청을 조회
}
private void cacheRequest(String request) {
// Redis 캐시에 요청을 저장하는 로직 (가상의 예제)
}
}
이렇게 사용
// 사용 예시
public class DecoratorPatternDemo {
public static void main(String[] args) {
RequestHandler handler = new BasicRequestHandler();
handler = new LoggingDecorator(handler);
handler = new AuthenticationDecorator(handler);
handler = new RedisCachingDecorator(handler);
handler.handleRequest("GET /home");
}
}
//출력
//Logging request: GET /home
//Authenticating request: GET /home
//Caching request: GET /home
//Handling request: GET /home
예시에서는 실제 인증로직이나 기타 다른 부분에 대해서는 구현하지 않았지만 예제 소스코드를 확인해 보면 요청 처리 로직을 변경하지 않고 필요한 기능을 유연하게 확장하여 처리할 수 있는 것을 확인할 수 있다.
이처럼 데코레이터 패턴을 이용하면 동적으로 새로운 기능들을 추가할 수 있다.
예를 들어 GUI를 구현할 때 버튼이나 텍스트 필드 등의 컴포넌트에 스크롤, 테두리 등 다양한 기능을 동적으로 추가하는 경우가 있을 것이다. 혹은 데이터 스트림 처리를 할 때 버퍼링이나 필터링 등 다양한 기능을 추가하는 경우가 발생하는데 이때 데코레이터 패턴을 이용하여 구현하기도 한다.
'Design Pattern > 구조 패턴(Structural Patterns)' 카테고리의 다른 글
플라이웨이트(Flyweight) 패턴 - 구조 패턴 (Structural Patterns) (0) | 2024.05.23 |
---|---|
퍼사드(Facade) 패턴 - 구조 패턴 (Structural Patterns) (0) | 2024.05.23 |
컴포지트(Composite) 패턴 - 구조 패턴 (Structural Patterns) (0) | 2024.05.23 |
브리지(Bridge) 패턴 - 구조 패턴 (Structural Patterns) (0) | 2024.05.23 |
어댑터(Adapter) 패턴 - 구조 패턴 (Structural Patterns) (0) | 2024.05.16 |