-
Spring Core - 템플릿 메서드 패턴(예제3, 적용1, 적용2)FrameWork/Spring&Spring-boot 2024. 3. 6. 14:04
템플릿 메서드 패턴 - 예제3
익명 내부 클래스 사용하기
템플릿 메서드 패턴은 SubClassLogic1, SubClassLogic2 처럼 클래스를 계속 만들어야 하는 단점이 잇다. 익명 내부 클래스를 사용하면 이런 단점을 보완할 수 있다.
익명 내부 클래스를 사용하면 객체 인스턴스를 생성하면서 동시에 생성할 클래스를 상속 받은 자식 클래스를 정의할 수 있다. 이 클래스는 SubClassLogic1 처럼 직접 지정하는 이름이 없고 클래스 내부에 선언되는 클래스여서 익명 내부클래스라 한다.
/** * 템플릿 메서드 패턴, 익명 내부 클래스 사용 */ @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("비지니스 로직2 실행"); } }; log.info("클래스 이름2 ={}",template1.getClass()); template1.execute(); }
실행결과
실행 결과를 보면자바가 임의로 만들어주는 익명 내부클래스의 이름은 TemplateMethodTest$1, TemplateMethodTest$2인 것을 확인할 수 있다. TemplateMethodTest 클래스 안에서 익명 클래스로 정의되었기 때문에 자바가 임의로 클래스 이름을 만들어 준것이다.
템플릿 메서드 패턴 - 적용1
AbstractTemplate
package com.spring.core.trace.template; import com.spring.core.logTrace.LogTrace; import com.spring.core.trace.TraceStatus; public abstract class AbstractTemplate<T>{ private final LogTrace trace; public AbstractTemplate(LogTrace trace) { this.trace = trace; } public T execute(String meassge){ TraceStatus status = null; try { status = trace.begin(meassge); T result = call(); trace.end(status); return result; }catch (Exception e){ trace.exception(status,e); throw e; } } public abstract T call(); }
- AbstracTemplate은 템플릿 메서드 패턴에서 부모 클래스이고, 템플릿 역할을 한다.
- <T> 제네릭을 사용했다. 변환 타입을 정의한다.
- 객체를 생성할 때 내부에서 사용할 LogTrace trace를 전달 받는다.
- 템플릿 코드 중간에 call() 메서드를 통해서 가변적인 부분을 처리한다.
- abstract T call()은 변하는 부분을 처리하는 메서드이다. 이 부분은 상속으로 구현해야 한다.
OrderControllerV4
@RestController @RequiredArgsConstructor public class OrderControllerV4 { private final OrderServiceV4 orderService; private final LogTrace trace; @GetMapping("/v4/order") public String createOrder(String itemId){ AbstractTemplate<String> template = new AbstractTemplate<>(trace) { @Override public String call() { orderService.orderItem(itemId); return "ok"; } }; return template.execute("OrderController.createOrder"); } }
- AbstractTemplate<String>
- 제네릭 String 으로 설정했다. AbstractTemplate의 반환 타입은 String된다.
- 익명 내부 클래스
- 익명 내부 클래스를 사용한다. 객체를 생성하면서 AbstractTemplate를 상속받은 자식 클래스를 정의했다.
- 따라서 별도의 자식 클래스를 직접 만들지 않아도 된다.
- template.execute("OrderController.createOrder()")
- 템플릿을 실행하면서 로그로 남길 message를 전달한다.
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 public Void call() { orderRepository.save(itemId); return null; } }; template.execute("OrderService.orderItem()"); } }
- AbstractTemplate<Void>
- 제네릭에서 반환 타입이 필요한데, 반환할 내용일 없으면 Void타입을 사용하고 null을 반환하면 된다. 참고로 제네릭은 기본 타입인 void, int 등을 선언할 수 없다.
정상 실행
- http://localhost:7000/v4/order?itemId=hello
정상 실행 로그
템플릿 메서드 패턴 - 적용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 코드 //template method 패턴 public void orderItem(String itemId){ AbstractTemplate<Void> template = new AbstractTemplate<>(trace) { @Override public Void call() { orderRepository.save(itemId); return null; } }; template.execute("OrderService.orderItem()"); }
- OrderServiceV0 : 핵심 기능만 있다.
- OrderServiceV3 : 핵심 기능과부가 기능이 함께 섞여 있다.
- OrderServiceV4 : 핵심 기능과 템플릿을 호출하는 코드가 섞여 있다.
v4는 템플릿 메서드 패턴을 사용한 덕분에 핵심 기능에 좀 더 집중할 수 있게 되었다.
좋은 설계란?
좋은 설계라는 것은 무엇일까? 수 많은 멋진 정의가 있겠지만, 진정한 좋은 설계는 바로 변경이 일어날때 자연스럽게 드러난다. 지금까지는 로그를 남기는 부가적인 코드를 하나로 모아서 모듀화하고, 비지니스 로직을 분리했다. 여기서 만약 로그를 남기는 로직을 변경해야 한다고 생각해보자. 그래서 AbstractTemplate 코드를 변경해야 한다면 단순히 AbstractTemplate 코드만 변경하면 된다.
템플릿이 없는 v3 상태에서 로그를 남기는 로직을 변경해야 한다고 생각하면 굉장히 오랜 시간이 들고, 그 과정에서 문제와 오류가 동반할 수 있다.
☝️단일 책임 원칙(SRP)
V4는 단순히 템플릿 메서드 패턴을 적용해서 소스코드 몇줄을 줄인 것이 전부가 아니다.
로그를 남기는 부분에 단일 책임 원칙(SRP)을 지킨 것이다. 변경 지점을 하나로 모아서 변경에 쉽게 대처할 수 있는 구조를 만든 것이다.
[출처 - 스프링 핵심원리-고급편, 저 김영한]
스프링 핵심 원리 - 고급편 강의 - 인프런
스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기 📢 수강
www.inflearn.com
'FrameWork > Spring&Spring-boot' 카테고리의 다른 글
Spring Core - 템플릿 콜백 패턴(시작, 적용, 정리) (0) 2024.03.07 Spring Core - 전략 패턴(시작, 예제1, 예제2) (1) 2024.03.07 Spring Core - 템플릿 메서드 패턴(시작, 예제1, 예제2, 정의) (1) 2024.03.06 Spring Core - 쓰레드 로컬 동기화(적용, 주의사항) (0) 2024.03.06 Spring Core - 쓰레드 로컬(동시성문제2) (0) 2024.03.05