<Spring> 템플릿 메서드 패턴

라모스·2022년 6월 19일
0

Spring☘️

목록 보기
11/18
post-thumbnail

템플릿 메서드 패턴

핵심 기능 vs 부가 기능

  • 핵심 기능: 해당 객체가 제공하는 고유의 기능
  • 부가 기능: 핵심 기능을 보조하기 위해 제공되는 기능으로 단독으로 사용되지는 않고, 핵심 기능과 함께 사용된다.
    ex) 로그 추적 로직, 트랜잭션 기능
TraceStatus status = null;
try {
    status = trace.begin("message");
    // 핵심 기능 호출
    trace.end(status);
} catch (Exception e) {
    trace.exception(status, e);
    throw e;
}

패턴 자체가 위와 같이 동일한 패턴으로 반복된다면, 부가 기능과 관련된 코드를 별도의 메서드로 뽑아내면 될 것 같다. 하지만, try-catch는 물론이고 핵심 기능 부분이 중간에 있어서 단순하게 메서드로 추출하는 것은 어렵다.

변하는 것과 변하지 않는 것을 분리하는 것이 좋은 설계이다.
여기서 핵심 기능 부분은 변하고, 로그 추적기를 사용하는 부분은 변하지 않는다. 이 둘을 분리해서 모듈화해야 한다.

템플릿 메서드 패턴(Template Method Pattern)은 이런 문제를 해결하는 디자인 패턴이다.

정의

"작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다. 템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있습니다."
[GoF의 디자인 패턴 中]

부모 클래스에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는 것이다. 이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고 특정 부분만 재정의할 수 있다. 결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.

적용 예시

기존에 공부했던 로그 추적기 로직에 패턴을 적용해보도록 하자.

  • AbstractTemplate
public abstract class AbstractTemplate<T> {
    private final LogTrace trace;
    
    public AbstractTemplate(LogTrace trace) {
        this.trace = trace;
    }
    
    pubilc T execute(String message) {
        TraceStatus status = null;
        try {
            status.trace.begin(message);
            
            // 로직 호출
            T result = call();
            
            trace.end(status);
            return result;
        } catch (Exception e) {
            trace.exception(status, e);
            throw e;
        }
    }
    
    protected abstract T call();
}

OrderControllerV4

@RestController
@RequiredArgsConstructor
public class OrderControllerV4 {
    private final OrderServiceV4 orderService;
    private final LogTrace trace;
    
    @GetMapping("/v4/request")
    public String request(String itemId) {
        AbstractTemplate<String> template = new AbstractTemplate<>(trace) {
            @Override
            protected String call() {
                orderService.orderItem(itemId);
                return "ok";
            }
        };
        
        return template.execute("OrderController.request()");
    }
}

로그를 남기는 부분을 모아서 하나로 모듈화하고, 비즈니스 로직 부분을 분리했다. 만약 로그를 남기는 로직을 변경해야 한다면 AbstractTemplate 코드만 변경하면 된다. 이처럼 변경이 일어날 때 쉽게 대처할 수 있는 구조를 만드는 것이 좋은 설계이다. 로그를 남기는 부분에 단일 책임 원칙(SRP)을 지키고 있다.

한계

하지만, 템플릿 메서드 패턴은 상속을 사용한다. 따라서 상속에서 오는 단점들을 그대로 안고간다.

  • 특히 자식 클래스가 부모 클래스와 컴파일 시점에 강하게 결합되는 문제가 있다. 이것은 의존관계에 대한 문제이다. 자식 클래스 입장에선 부모 클래스의 기능을 전혀 사용하지 않는다. 그럼에도 불구하고 템플릿 메서드 패턴을 위해 자식 클래스는 부모 클래스를 상속 받고 있다.
  • 상속을 받는다는 것은 특정 부모 클래스를 의존하고 있다는 것이다. 자식 클래스의 extends 다음에 바로 부모 클래스가 코드상에 지정되어 있다. 따라서 부모 클래스의 기능을 사용하든 사용하지 않든 간에 부모 클래스를 강하게 의존하면 된다. 여기서 강하게 의존한다는 뜻은 자식 클래스의 코드에 부모 클래스의 코드가 명확하게 적혀있다는 뜻이다.
  • 자식 클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는데, 부모 클래스를 알아야한다. 이것은 좋은 설계가 아니다. 또한 잘못된 의존관계 때문에 부모 클래스를 수정하면, 자식 클래스에도 영향을 줄 수 있다.
  • 템플릿 메서드 패턴은 상속 구조를 사용하기 때문에, 별도의 클래스나 익명 내부 클래스를 만들어야 하는 부분도 복잡하다.

이 패턴과 비슷한 역할을 하면서 상속의 단점을 제거할 수 있는 디자인 패턴이 바로 전략 패턴(Strategy Pattern)이다.

References

profile
Step by step goes a long way.

0개의 댓글