BackEnd/Spring

[Spring] 데코레이터 패턴

샤아이인 2022. 8. 10.

내가 공부한것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼겸 상세히 기록하고 얕은부분들은 가겹게 포스팅 하겠습니다.

 

1. 데코레이터 패턴 - 예제 코드1

간단하게 예제 코드를 만들어보자! 이전 Proxy 패턴과 거의 유사하다.

또한 코드가 매우 간단하여 설명은 생략한다.

 

출처 - 인프런 김영한 스프링

 

▶ Component

public interface Component {
    String operation();
}

 

▶ RealComponent

@Slf4j
public class RealComponent implements Component {

    @Override
    public String operation() {
        log.info("RealComponent 실행");
        return "data";
    }
}

 

▶ DecoratorPatternClient

@Slf4j
public class DecoratorPatternClient {

    private Component component;

    public DecoratorPatternClient(Component component) {
        this.component = component;
    }

    public void execute() {
        String result = component.operation();
        log.info("result={}", result);
    }
}

 

이를 실행하는 테스트 코드는 다음과 같다.

@Slf4j
public class DecoratorPatternTest {
    
    @Test
    public void no_decorator_test() {
        RealComponent component = new RealComponent();
        DecoratorPatternClient client = new DecoratorPatternClient(component);
        client.execute();
    }
}

실핼 결과는 다음과 같다.

 

2. 데코레이터 패턴 - 예제 코드2

앞서 프록시 패턴에서 캐시를 통한 접근 제어를 알아보았다.

이번에는 프록시를 활용해서 부가 기능을 추가해보자. 이렇게 프록시로 부가 기능을 추가하는 것을 데코레이터 패턴이라 한다.

 

출처 - 인프런 김영한 고급편

우리의 기존 메시지를 꾸며주는 처리를 하는 MessageDecorator를 만들어보자!

@Slf4j
public class MessageDecorator implements Component {

    private Component component;

    public MessageDecorator(Component component) {
        this.component = component;
    }

    @Override
    public String operation() {
        log.info("MessageDecorator 실행");

        String result = component.operation();
        String decoratedResult = "***" + result + "***";
        log.info("MessageDecorator 꾸미기 적용 전={}, 적용 후={}", result, decoratedResult);
        return decoratedResult;
    }
}

Component 인터페이스를 구현한다. 따라서 사용하는 Client는 RealComponent인지? MessageDecorator인지? 구별하지 못한다.

또한 프록시가 호출해야 하는 대상을 component 에 저장한다.
operation() 을 호출하면 프록시와 연결된 대상을 호출( component.operation()) 하고, 그 응답 값에 *** 을 더해서 반환한다.

 

이를 테스트 코드를 통하여 확인해보자!

@Slf4j
public class DecoratorPatternTest {

    @Test
    void decorator1() {
        Component realComponent = new RealComponent();
        Component messageDecorator = new MessageDecorator(realComponent);
        DecoratorPatternClient client = new DecoratorPatternClient(messageDecorator);
        client.execute();
    }
}

client -> messageDecorator -> realComponent 의 객체 의존 관계를 만들고, 실행한 결과는 다음과 같다.

원래는 "data"라고만 출력되던 문자열이, "***data***"로 앞뒤에 별표가 추가되어 나온다.

 

3. 데코레이터 패턴 - 예제 코드3

이번에는 기존 데코레이터에 더해서 실행 시간을 측정하는 기능까지 추가해보자.

출처 - 인프런 김영한 고급편

 

실행 시간을 출력해주는 TimeDecorator를 만들어보자!

 

▶ TimeDecorator

@Slf4j
public class TimeDecorator implements Component {

    private Component component;

    public TimeDecorator(Component component) {
        this.component = component;
    }

    @Override
    public String operation() {
        log.info("TimeDecorator 실행");
        long startTime = System.currentTimeMillis();
        String result = component.operation();
        long endTime = System.currentTimeMillis();

        long resultTime = endTime - startTime;
        log.info("TimeDecorator 종료, resultTime={}", resultTime);
        return result;
    }
}

 

이를 사용하는 테스트 코드는 다음과 같다.

@Test
void decorator2() {
    Component realComponent = new RealComponent();
    Component messageDecorator = new MessageDecorator(realComponent);
    TimeDecorator timeDecorator = new TimeDecorator(messageDecorator);
    DecoratorPatternClient client = new DecoratorPatternClient(timeDecorator);
    client.execute();
}

데코레이터를 연속하여 2개 사용하였다.

client -> timeDecorator -> messageDecorator -> realComponent 의 객체 의존관계를 설정하고 실행해보자!

 

출력 결과는 다음과 같다.

 

4. 프록시 패턴과 데코레이터 패턴 정리

우선 다음 그림을 살펴보자.

출처 - 인프런 김영한 고급편

 

우리가 만든 Decorator들을 보면 코드에 중복된 부분이 있다.

꾸며주는 역할을 하는 Decorator 들은 스스로 존재할 수 없다. 항상 꾸며줄 대상이 있어야 한다.

따라서 Decorator는 내부에 호출 대상인 component 를 필수적으로 가지고 있어야 한다.

 

즉, component 를 필드로 가지고있고, 항상 호출해야 한다.

다음 코드 부분이 중복되는 것 이다.

public class xxxDecorator implements Component {

    private Component component;

    public MessageDecorator(Component component) {
        this.component = component;
    }
}

이런 중복을 제거하기 위해 component 를 속성으로 가지고 있는 Decorator 라는 추상 클래스를 만드는 방법도 고민할 수 있다.

이렇게 하면 추가로 클래스 다이어그램에서 어떤 것이 실제 컴포넌트 인지, 데코레이터인지 명확하게 구분할 수 있다.

여기까지 고민한 것이 바로 GOF에서 설명하는 데코레이터 패턴의 기본 예제이다.

 

더 자세한 데코레이터 패턴의 글은 다음 내가 정리해둔 글을 읽어보길 권장한다.

https://blogshine.tistory.com/7

 

[Design Patterns] Decorator Pattern : 데코레이터 패턴

Head First Design Patterns 책을 읽으며 정리한 내용 입니다. 문제가 될시 글을 내리도록 하겠습니다! Decorator Pattern 이란? Decorator Pattern - 객체에 추가적인 요건을 동적으로 첨가한다. 데코레이터는..

blogshine.tistory.com

 

댓글