오봉이와 함께하는 개발 블로그
스프링 핵심 원리 - 고급편 > 템플릿 메서드 패턴 (적용 1, 적용 2, 정의) 본문
템플릿 메서드 패턴 - 적용 1
AbstractTemplate.java
public abstract class AbstractTemplate<T> {
private final LogTrace trace;
public AbstractTemplate(LogTrace trace) {
this.trace = trace;
}
public 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();
}
AbstractTemplate
은 템플릿 메서드 패턴에서 부모 클래스이고, 템플릿 역할을 한다.<T>
제네릭을 사용했다. 반환 타입 정의- 객체를 생성할 때 내부에서 사용할
LogTrace trace
를 전달 받는다. - 로그에 출력할
message
를 외부에서 파라미터로 전달받는다. - 템플릿 코드 중간에
call()
메서드를 통해서 변하는 부분을 처리한다. abstract T call()
은 변하는 부분을 처리하는 메서드이다. 이 부분은 상속으로 구현해야 한다.
OrderControllerV4
@GetMapping("/v4/request")
public String request(@RequestParam("itemId") String itemId) {
AbstractTemplate<String> template = new AbstractTemplate<>(trace) {
@Override
protected String call() {
orderService.orderItem(itemId);
return "ok";
}
};
return template.execute("OrderController.request()");
}
AbstractTemplate\<String\>
Generic
을String
으로 설정- 따라서
AbsctractTemplate
의 반환 타입은String
이 된다.
- 익명 내부 클래스
- 익명 내부 클래스를 사용한다.
- 객체를 생성하며
AbsctractTemplate
을 상속받은 자식 클래스를 정의- 별도의 클래스를 생성하지 않아도 된다.
template.execute("OrderController.request()")
- 템플릿을 실행하며 로그로 남길
message
전달
- 템플릿을 실행하며 로그로 남길
OrderServiceV4
public void orderItem(String itemId) {
AbstractTemplate<Void> template = new AbstractTemplate<>(trace) {
@Override
protected Void call() {
orderRepository.save(itemId);
return null;
}
};
template.execute("OrderService.orderItem()");
}
AbstractTemplate<Void>
Generic
을 사용하여 반환 타입이 필요한데, 반환할 내용이 없다면, Void 타입(클래스)을 사용하고null
을 반환하면 된다.
OrderRepositoryV4
public void save(String itemId) {
AbstractTemplate<Void> template = new AbstractTemplate<>(trace) {
@Override
protected Void call() {
//저장 로직
if (itemId.equals("ex")) {
throw new IllegalStateException("예외 발생!");
}
sleep(1000);
return null;
}
};
template.execute("OrderRepository.save()");
}
실행하면 잘 찍히는 것을 확인할 수 있다.
[3bb813c5] OrderController.request()
[3bb813c5] |-->OrderService.orderItem()
[3bb813c5] | |-->OrderRepository.save()
[3bb813c5] | |<--OrderRepository.save() time=1004ms
[3bb813c5] |<--OrderService.orderItem() time=1005ms
[3bb813c5] OrderController.request() time=1006ms
템플릿 메서드 패턴 - 적용 2
템플릿 메서드 패턴을 사용하여 변하는 코드와 변하지 않는 코드를 분리했다..
로그를 출력하는 템플릿 역할을 하는 변하지 않는 코드는 모두 AbstractTemplate
에 담아두고, 변하는 코드는 자식 클래스를 만들어 분리했다.
//OrderServiceV0 코드
public void orderItem(String itemId) {
orderRepository.save(itemId);
}
//OrderServiceV3 코드
public void orderItem(String itemId) {
TraceStatus status = null;
try {
status = trace.begin("OrderService.orderItem()"); orderRepository.save(itemId); //핵심 기능 trace.end(status);
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
//OrderServiceV4 코드
AbstractTemplate<Void> template = new AbstractTemplate<>(trace) {
@Override
protected Void call() {
orderRepository.save(itemId);
return null;
}
};
template.execute("OrderService.orderItem()");
좋은 설계
좋은 설계에 대한 정의는 수도 없이 많겠지만, 진가가 드러날 때는 변경이 일어날 때다.
로그를 남기는 부분을 모두 모아 하나로 모듈화 하고, 비즈니스 로직을 분리하였다.
만약 로그 남기는 코드가 분리되지 않아 여러 곳에 있을 때 로그 코드에 변경이 발생하면 모든 곳을 찾아서 변경해야 한다.
하지만 AbstractTemplate
에 로그 코드를 모아두었기 때문에 로그 기능이 변경되었을 때 AbstractTemplate
만 수정하면 된다.
템플릿 메서드 패턴 - 정의
GOF 디자인 패턴에서는 템플릿 메서드 패턴을 다음과 같이 정의한다.
템플릿 메서드 디자인 패턴의 목적은 다음과 같습니다. 작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다. 템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있습니다."
GOF 템플릿 메서드 패턴 정의
부모 클래스에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는 것이다.
이렇게 하 면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고, 특정 부분만 재정의할 수 있다.
결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것
문제
템플릿 메서드 패턴은 추상 클래스르 사용하여 상속을 사용한다. 따라서 상속의 문제를 그대로 안고가야 하는 단점이 존재한다.
자식 클래스가 부모 클래스에 너무 의존하게 된다. 한 클래스가 다른 클래스에 의존할 때는 해당 클래스 수정 시 높은 확률로 다른 클래스도 수정이 되어야 하고, 거기에 엮인 모든 클래스에 수정이 발생할 수도 있다.
그렇게 되면 특정 클래스의 특정 부분만 변경하고 싶은데, 엮인 모든 클래스가 변경되어 어디서 어떤 사이드 이펙트가 발생할지 모르는 상황이 된다.
또한 이전 예제들에서는 자식 클래스 입장에서는 부모 클래스의 기능을 사용하지 않는데 상속 받고 있다.
상속을 받는다는 것은 특정 부모 클래스를 의존하고 있다는 것이다.
자식 클래스의 extends
다음에 바로 부모 클래스가 코드상에 지정되어 있다.
따라서 부모 클래스의 기능을 사용하든 사용하지 않든 간에 부모 클래스를 강하게 의존하게 된다.
여기서 강하게 의존한다는 뜻은 자식 클래스의 코드에 부모 클래스의 코드가 명확하게 적혀 있다는 뜻이다.
UML에서 상속을 받으면 삼각형 화살표가 자식 -> 부모
를 향하고 있는 것은 이런 의존관계를 반영하는 것이다.
추가로 템플릿 메서드 패턴은 상속 구조를 사용하기 때문에, 별도의 클래스나 익명 내부 클래스를 만들어야 하는 부분도 복잡하다.
출처: 김영한 지식공유자님의 스프링 핵심 원리 고급편
'BE > Spring' 카테고리의 다른 글
스프링 핵심 원리 - 고급편 > 전략 패턴 (예제 2, 예제 3) (0) | 2024.05.10 |
---|---|
스프링 핵심 원리 - 고급편 > 전략 패턴 (시작, 예제 1) (0) | 2024.05.10 |
스프링 핵심 원리 - 고급편 > 템플릿 메서드 패턴 (시작, 예제 1, 예제 2) (0) | 2024.05.10 |
Spring Framework - 테스트 코드 작성 (기본 - mock, spy, MockBean, SpyBean, MockWebServer) (0) | 2024.03.31 |
스프링 핵심 원리 - 고급편_쓰레드 로컬(주의사항) (0) | 2024.03.17 |