[Java] 데코레이터 패턴 정리

Kai·2023년 12월 27일
0

디자인 패턴

목록 보기
5/5

☕ 개요


지난 글에서 이어서 프록시를 활용한 디자인 패턴인 데코레이터 패턴에 대해서 알아보겠다.

데코레이터 패턴은 프록시를 활용해서 어떠한 기능에 뭔가를 꾸며주고, 기능을 추가하는 패턴을 이야기한다.
프록시라는 개념을 사용하므로, 모양새 자체는 프록시 패턴과 매우 유사하지만, 이 둘의 차이점은 각각의 목적이 다르다는 것이다. 프록시 패턴은 클라이언트의 접근 제어가 목적이고, 데코레이터 패턴은 어떠한 기능에 추가적인 기능을 동적으로 추가하는 것에 있다.

그렇다면, 데코레이터 패턴에 대해서 예시 코드를 통해서 자세히 알아보자.


🧐 데코레이터 패턴 예시


1. 가정

어떤 데이터를 반환하는 서버의 기능이 있고, 우리는 이 기능에서 반환하는 결과를 조작하고 기능이 동작하는 데 걸리는 시간을 측정해야한다고 가정해보도록 하겠다.

2. Server 인터페이스 만들기

public interface Server {
    String run();
}

간단한 형태의 Server 인터페이스를 하나 만들어주자.

3. 구현 클래스 만들기

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class RealServer implements Server {
    @Override
    public String run() {
        log.info("RealServer 실행");
        return "Data";
    }
}

Server 인터페이스를 상속받은 실제 서버 역할을 할 클래스를 만들어주자.
기능 자체는 로깅을 하고, 데이터를 반환하는 아주 간단한 기능이다.

4. MessageDecorator 만들기

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
public class MessageDecorator implements Server {

    private final Server server;

    @Override
    public String run() {
        log.info("MessageDecorator 실행");
        String result = server.run();
        String decoratedResult = "***" + result + "***";
        return decoratedResult;
    }
}

서버에서 반환받은 결과에 ***을 앞뒤로 붙여주는 기능을 갖고 있는 데코레이터를 하나 만들어주자.

5. TimeDecorator 만들기

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
public class TimeDecorator implements Server {

    private final Server server;

    @Override
    public String run() {
        log.info("TimeDecorator 실행");
        long startMs = System.currentTimeMillis();
        String result = server.run();
        long endMs = System.currentTimeMillis();
        String decoratedResult = result + " " + (endMs - startMs) + "ms";
        return decoratedResult;
    }
}

서버에서 반환받은 결과에 기능이 수행되는 데 걸린 시간을 추가해주는 기능을 갖고 있는 데코레이터를 하나 만들어주자.

6. 클라이언트 클래스 만들기

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
public class SomeClient {

    private final Server server;

    public void request() {
        String result = server.run();
        log.info("result={}", result);
    }

}

서버의 기능을 실행하는 메서드 하나만을 갖고 있는 간단한 클라이언트 역할의 클래스를 만들어주자.

7. 테스트 코드로 동작확인

import hello.springaop.pattern.decorator.code.MessageDecorator;
import hello.springaop.pattern.decorator.code.RealServer;
import hello.springaop.pattern.decorator.code.SomeClient;
import hello.springaop.pattern.decorator.code.TimeDecorator;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class DecoratorPatternTest {

    @Test
    @DisplayName("데코레이터 미적용")
    public void noDecorator() throws Exception {
        RealServer realComponent = new RealServer();
        SomeClient client = new SomeClient(realComponent);

        client.request();
    }

    @Test
    @DisplayName("데코데이터 적용")
    public void decorator() throws Exception {
        RealServer realComponent = new RealServer();
        MessageDecorator messageDecorator = new MessageDecorator(realComponent);
        TimeDecorator timeDecorator = new TimeDecorator(messageDecorator);
        SomeClient client = new SomeClient(timeDecorator);

        client.request();
    }

}

자 이제, 데코레이터를 적용한 코드와 그렇지 않은 코드를 작성하고 이 둘을 실행해서 각각이 어떻게 다른 결과를 출력하는지 확인해보자.

먼저, 데코레이터를 적용하지 않은 코드는 당연하게도 단순히 결과만을 반환하고 출력하는 것을 확인할 수 있다.

반면에 2개의 데코레이터를 적용한 코드의 결과에는 예상한 것처럼 ***이라는 글자가 추가되어 있고, 소요 시간도 추가되어 있는 것을 확인할 수 있다.

즉, 클라이언트와 서버 역할을 하는 클래스의 코드에 손을 대지 않고, 데코레이터 클래스들을 통해서 새로운 기능을 추가해줄 수 있다는 것을 알 수 있다. 👍


🙏 참고


0개의 댓글