내가 공부한것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼겸 상세히 기록하고 얕은부분들은 가겹게 포스팅 하겠습니다.
1. 템플릿 메서드 패턴 - 예제1
이번시간은 TemplateMethod Pattern을 적용할 예제를 만들어보자.
@Slf4j
public class TemplateMethodTest {
@Test
void templateMethodV0() {
logic1();
logic2();
}
private void logic1() {
long startTime = System.currentTimeMillis();
//비즈니스 로직 실행
log.info("비즈니스 로직1 실행");
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
private void logic2() {
long startTime = System.currentTimeMillis();
//비즈니스 로직 실행
log.info("비즈니스 로직2 실행");
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
}
간단한 코드이다.
실행 결과는 다음과 같다.
logic1() 과 logic2() 는 시간을 측정하는 부분과 비즈니스 로직을 실행하는 부분이 함께 존재한다.
템플릿 메서드 패턴 에서는 변하는 부분과, 변하지 않는 부분을 찾는것이 가장 중요하다.
변하는 부분: 비즈니스 로직
변하지 않는 부분: 시간 측정
이제 템플릿 메서드 패턴을 사용해서 변하는 부분과 변하지 않는 부분을 분리해보자.
2. 템플릿 메서드 패턴 - 예제2
우선 template를 만들어보자.
▶ AbstractTemplate
@Slf4j
public abstract class AbstractTemplate {
public void execute() {
long startTime = System.currentTimeMillis();
//비즈니스 로직 실행
call();
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
protected abstract void call();
}
템플릿 메서드 패턴은 이름 그대로 템플릿을 사용하는 방식이다. 템플릿은 기준이 되는 거대한 틀이다.
템플릿이라는 틀에 변하지 않는 부분을 몰아둔다. 그리고 일부 변하는 부분(call 메서드)을 별도로 호출해서 해결한다.
템플릿 메서드 패턴은 부모 클래스에 변하지 않는 템플릿 코드를 둔다. 전반적인 고정된 틀에 해당된다.
그리고 변하는 부분은 자식 클래스에 두고 상속과 오버라이딩을 사용해서 처리한다.
이제 AbstractTemplate를 상속받는 구현체를 만들어보자!
▶ SubClassLogic1
@Slf4j
public class SubClassLogic1 extends AbstractTemplate {
@Override
public void call() {
log.info("비즈니스 로직1 실행");
}
}
▶ SubClassLogic2
@Slf4j
public class SubClassLogic2 extends AbstractTemplate {
@Override
public void call() {
log.info("비즈니스 로직2 실행");
}
}
구현체에서는 공통화된 템플릿 부분이 아닌, 자식 부분에서 템플릿이 호출하는 대상인 call() 메서드를 오버라이딩 한다.
이를 실행하는 테스트 코드를 살펴보자
/**
* 템플릿 메서드 패턴 적용
*/
@Test
void templateMethodV1() {
AbstractTemplate template1 = new SubClassLogic1();
template1.execute();
AbstractTemplate template2 = new SubClassLogic2();
template2.execute();
}
실행 결과는 다음과 같다.
template1.execute() 를 실행하면 템플릿 로직인 AbstractTemplate.execute() 를 실행한다.
여기서 중간에 call() 메서드를 호출하는데, 이 부분이 오버라이딩 되어있다.
따라서 현재 인스턴스인 SubClassLogic1 인스턴스의 SubClassLogic1.call() 메서드가 호출된다.
즉, 큰 템플릿의 실행 흐름 줄기에서 변화를 주고 싶은 부분만 개발자가 변화를 줄수 있게 된 것 이다.
다형성을 통해 변하는 부분과, 변하지 않는 부분을 나눈 것 이다.
3. 템플릿 메서드 패턴 - 예제3
템플릿 메서드 패턴을 단점이 있는데, SubClassLogic1 , SubClassLogic2 처럼 클래스를 계속 만들어야 하는 단점이 있다.
이를 익명 내부 클래스를 사용하면 이런 단점을 보완할 수 있다.
@Slf4j
public class TemplateMethodTest {
// 일부 생략...
@Test
void templateMethodV2() {
AbstractTemplate template1 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
};
log.info("클래스 이름1={}", template1.getClass());
template1.execute();
AbstractTemplate template2 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
};
log.info("클래스 이름2={}", template2.getClass());
template2.execute();
}
}
실행 결과는 다음과 같다.
실행 결과를 보면 자바가 임의로 만들어주는 익명 내부 클래스 이름은 TemplateMethodTest$1 , TemplateMethodTest$2 인 것을 확인할 수 있다.
이는 익명 클래스가 TemplateMethodTest class 내부에 정의되었기 때문이다.
$1, $2 는 임의로 생성된 이름이다.
4. 템플릿 메서드 패턴 - 적용
템플릿 메서드 패턴을 통해 우리의 LogTrace를 개선해보자!
우선 AbstractTemplate를 만들자.
▶ AbstractTemplate
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();
}
이를 사용하여 controller, service, repository를 변경해보자!
▶ OrderControllerV4
@RestController
@RequiredArgsConstructor
public class OrderControllerV4 {
private final OrderServiceV4 orderService;
private final LogTrace trace;
@GetMapping("/v4/request")
public String request(@RequestParam String itemId) {
AbstractTemplate<String> template = new AbstractTemplate<>(trace) {
@Override
protected String call() {
orderService.orderItem(itemId);
return null;
}
};
return template.execute("OrderController.request()");
}
}
익명 내부 클래스를 사용한다. 객체를 생성하면서 AbstractTemplate 를 상속받은 자식 클래스를 정의했다.
엄밀하게는 AbstractTemplate를 상속한 자식 class가 정의되고, 그 다음 객체로 생성하는 것 이다.
▶ OrderServiceV4
@Service
@RequiredArgsConstructor
public class OrderServiceV4 {
private final OrderRepositoryV4 orderRepository;
private final LogTrace trace;
public void orderItem(String itemId) {
AbstractTemplate<Void> template = new AbstractTemplate<>(trace) {
@Override
protected Void call() {
orderRepository.save(itemId);
return null;
}
};
template.execute("OrderService.orderItem()");
}
}
▶ OrderRepositoryV4
@Repository
@RequiredArgsConstructor
public class OrderRepositoryV4 {
private final LogTrace trace;
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()");
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
실행시 의도한것 처럼 정상적으로 Log를 출력해주는 것을 확인할 수 있다!
우리의 TemplateMethodPattern은 변경지점을 한곳으로 모아서 변경에 쉽게 대처할 수 있다.
예를 들어 로그를 출력하는 형식이 달라진다면, V4버전 에서는 템플릿 부분만 수정하면 된다!
이와는 대조적으로 V3는 모든 controller, service, repository를 전부 수정해야 한다.
즉, SRP(단일 책임 원칙)을 지켰다고 할 수 있다!
5. 템플릿 메서드 패턴 - 정의
템플릿 메서드 디자인 패턴의 목적은 다음과 같습니다.
"작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다.
템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있습니다." [GOF]
부모 클래스에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는 것이다.
이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고, 특정 부분만 재정의할 수 있다.
결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.
문제는... 상속인데... 상속의 단점을 그대로 가져온다...
자식 클래스가 부모 클래스와 컴파일 시점에 강하게 결합되는 문제가 도사린다.
또한 자식 클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는다.
그럼에도 불구하고 템플릿 메서드 패턴을 위해 자식 클래스는 부모 클래스를 상속 받고 있다.
자식 클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는데, 부모 클래스를 알아야한다.
이것은 좋은 설계가 아니다. 그리고 이런 잘못된 의존관계 때문에 부모 클래스를 수정하면, 자식 클래스에도 영향을 줄 수 있다.
'BackEnd > Spring' 카테고리의 다른 글
[Spring] 템플릿 콜백 패턴 (0) | 2022.08.07 |
---|---|
[Spring] 전략 패턴 (0) | 2022.08.07 |
[Spring] 쓰레드 로컬 - ThreadLocal - 2 (0) | 2022.08.05 |
[Spring] 쓰레드 로컬 - ThreadLocal - 1 (0) | 2022.08.04 |
[Spring] 예제 만들기 (0) | 2022.08.03 |
댓글