You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
packagehello.advanced.app.v3;
importhello.advanced.trace.TraceStatus;
importhello.advanced.trace.logtrace.LogTrace;
importlombok.RequiredArgsConstructor;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.RestController;
@RestController// @Controller + @ResponseBody@RequiredArgsConstructorpublicclassOrderControllerV3 {
privatefinalOrderServiceV3orderService;
privatefinalLogTracetrace;
@GetMapping("/v3/request")
publicStringrequest(StringitemId) {
TraceStatusstatus = null;
try {
status = trace.begin("OrderController.request()"); //여기부분orderService.orderItem(itemId);
trace.end(status); //예외터졌을때 이 부분 안나옴return"OK"; // @RestController 이기 때문에 문자
} catch (Exceptione) {
trace.exception(status, e); // 예외를 가지고 있음throwe;//예외를 다시 던져줘야 한다.
}
}
}
배보다 배꼽이 큰 현상
V0는 해당 메서드가 실제 처리해야 하는 핵심 기능만 깔끔하게 남아있다.
반면에 V3는 핵심기능보다 로그를 출력해야 하는 부가기능 코드가 훨씬 더 많고 복잡하다.
핵심 기능 vs 부가 기능
핵심기능
핵심 기능은 해당 객체가 제공하는 고유의 기능이다. 예를 들어서 ‘orderService’의 핵심기능은 주문 로직이다. 메서드 단위로 보면 orderService.orderItem() 의 핵심기능은 주문 데이터를 저장하기 위해 리포지토리를 호출하는 orderRepository.save(itemId) 코드가 핵심 기능이다.
부가기능
부가기능은 핵심기능을 보조하기 위해서 제공되는 기능이다. 예를 들어서 로그 추적 로직, 트랜잭션 기능이 있다. 이러한 부가 기능은 단독으로 사용되지는 않고, 핵심기능과 함께 사용된다. 예를 들면 로그 추적 기능은 어떤 핵심 기능이 호출되었는지 로그를 남기기 위해 사용한다. 그러니까 핵심 기능을 보조하기 위해 존재한다.
V0 는 핵심기능만 있지만 로그추적기를 추가한 V3 코드는 핵심기능과 부가기능이 섞여있다.
V3를 보면 로그 추적기의 도입으로 핵심 기능 코드보다 부가 기능을 처리하기 위한 코드가 더 많아졌다.
템플릿 메서드 패턴은 이름 그대로 템플릿을 사용하는 방식이다. 템플릿은 기준이 되는 거대한 틀이다. 템플릿이라는 틀에 반하지 않는 부분을 몰아둔다. 그리고 일부 변하는 부분을 별도로 호출해서 해결한다.
AbstractTemplate 코드를 보자. 변하지 않는 부분인 시간 측정 로직을 몰아둔 것을 확인할 수 있따. 이제 이것이 하나의 템플릿이 된다. 그리고 템플릿 안에서 변하는 부분은 ‘call()’ 메서드를 호출해서 처리한다.템플릿 메서드 패턴은 부모 클래스에 변하지 않는 템플릿 코드를 둔다. 그리고 변하는 부분은 자식 클래스에 두고 상속과 오버라이딩을 사용해서 처리한다.
좋은 설계란 무엇일까 수많은 멋진 정의가 있겠지만, 진정한 좋은 설계는 바로 변깅이 일어날때 자연스럽게 드러난다.
지금까지 로그를 남기는 부분을 모아서 하나로 모듈화하고 비즈니스 로직 부분을 분리했다. 여기서 만약 로그를 남기는 로직을 변경해야 한다고 생각해보자. 그래서 AbstractTemplate 코드를 변경해야 한다고 가정해보자. 단순히 AbstractTemplate 코드만 변경하면 도니다.
템플릿이 없는 V3 상태에서 로그를 남기는 로직을 변경해야 한다고 생각해보자. 이 경우 모든 클래스를 다 찾아서 고쳐야 한다. 클래스가 수백개라면 생각만 해도 끔찍하다.
단일 책임 원칙(SRP)
v4는 단순히 템플릿 메서드 패턴을 적용해서 소스코드 몇줄을 줄인 것이 전부가 아니다.
로그를 남기는 부분에 단일 책임 원칙을 지킨 것이다. 변경 지점을 하나로 모아서 변경에 쉽게 대처할 수 있는 구조를 만든것이다.
템플릿 메서드 패턴 - 정의
GOF 디자인 패턴에서는 템플릿 메서드 패턴을 다음과 같이 정의했다.
템플릿 메서드 디자인 패턴의 목적은 다음과 같다.
작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기한다. 템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있다. [GOF]
GOF 템플릿 메서드 패턴 정의
풀어서 설명하면 다음과 같다.
부모 클래스에 알고리즘의 골격인 템플릿을 정의하고 일부 변경되는로직은 자식 클래스에 정의하는 것이다. 이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고 특정 부분만 재정의할 수 있다. 결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.
하지만
템플릿 메서드 패턴은 상속을 사용한다. 따라서 상속에서 오는 단점들을 그대로 안고간다. 특히 자식 클래스가 부모클래스와 컴파일 시점에 강하게 결합되는 문제가 있다. 이것은 의존관계에 대한 문제이다. 자식 클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는다.
이번 장에서 지금까지 작성했던 코드를 떠올려보자. 자식 클래스를 작성할때 부모 클래스의 기능을 사용한 것이 있었던가?
그럼에도 불구하고 템플릿 메서드 패턴을 위해 자식 클래스는 부모 클래스를 상속 받고 있다.
상속을 받는다는 것은 특정 부모 클래스를 의존하고 있다는 것이다. 자식 클래스의 extends 다음에 바로 부모 클래스가 코드상에 지정되어 있다. 따라서 부모 클래스의 기능을 사용하든 사용하지 않는 간에 부모 클래서를 강하게 의존하게 된다. 여기서 강하게 의존한다는 뜻은 자식 클래스의 코드에 부모 클래스의 코드가 명확하게 적혀있다는 뜻이다.
UML 에서 상속을 받으면 삼각형 화살표가 자식 → 부모 를 향하고 있는 것은 이런 의존관계를 반영하는 것이다. 자식 클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는데 부모 클래스를 알아야한다. 이것은 좋은 설계가 아니다. 그리고 이런 잘못된 의존관계 때문에 부모 클래스를 수정하면, 자식 클래스에도 영향을 줄 수 있다.
추가로 템플릿 메서드 패턴은 상속 구조를 사용하기 때문에 별도의 클래스나 익명 내부 클래스를 만들어야 하는 부분도 복잡하다.
지금까지 설명한 이런 부분들을 더 깔끔하게 개선하려면 어떻게 해야할까?
템플릿 메서드 패턴과 비슷한 역할을 하면서 상속의 단점을 제거할 수 있는 디자인 패턴이 바로 전략 패턴이다(Strategy Pattern).
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
템플릿 메서드 패턴 - 시작
지금까지 로그 추적기를 열심히 만들었다.
요구사항도 만족하고 파라미터를 넘기는 불편함을 제거하기 위해 스레드 로컬도 도입했다.
그런데 로그 추적기를 막상 프로젝트에 도입하려고 하니 개발자들의 반대 목소리가 높다.
로그 추적기 도입 전과 도입 후의 코드를 비교해보자.
배보다 배꼽이 큰 현상
V0는 해당 메서드가 실제 처리해야 하는 핵심 기능만 깔끔하게 남아있다.
반면에 V3는 핵심기능보다 로그를 출력해야 하는 부가기능 코드가 훨씬 더 많고 복잡하다.
핵심 기능 vs 부가 기능
핵심기능
핵심 기능은 해당 객체가 제공하는 고유의 기능이다. 예를 들어서 ‘orderService’의 핵심기능은 주문 로직이다. 메서드 단위로 보면
orderService.orderItem()의 핵심기능은 주문 데이터를 저장하기 위해 리포지토리를 호출하는orderRepository.save(itemId)코드가 핵심 기능이다.부가기능
부가기능은 핵심기능을 보조하기 위해서 제공되는 기능이다. 예를 들어서 로그 추적 로직, 트랜잭션 기능이 있다. 이러한 부가 기능은 단독으로 사용되지는 않고, 핵심기능과 함께 사용된다. 예를 들면 로그 추적 기능은 어떤 핵심 기능이 호출되었는지 로그를 남기기 위해 사용한다. 그러니까 핵심 기능을 보조하기 위해 존재한다.
V0 는 핵심기능만 있지만 로그추적기를 추가한 V3 코드는 핵심기능과 부가기능이 섞여있다.
V3를 보면 로그 추적기의 도입으로 핵심 기능 코드보다 부가 기능을 처리하기 위한 코드가 더 많아졌다.
만약 클래스가 수백개라면?
이 문제를 좀더 효율적으로 처리할 수 있는 방법이 있을까?
V3 코드를 유심히 잘 살펴보면 다음과 같이 동일한 패턴이 있다.
Service
Controller
동일한 패턴이 있다.Controller, Service, Repository 모두 핵심 기능만 차이가 있는거다.
구조는 모두 동일하다.
부가 기능과 관련된 코드가 중복이니 중복을 별도의 메서드로 뽑아내면 될 것 같다.
그런데, try~catch는 물론이고 핵심기능 부분이 중간에 있어서 단순하게 메서드로 추출하는 것은 어렵다.
변하는 것과 변하지 않는 것은 분리
좋은 설계는 변하는 것과 변하지 않는 것을 분리하는 것이다.
템플릿 메서드 패턴 - 예제1
템플릿 메서드 패턴을 쉽게 이해하기 위해 단순한 예제코드를 만들어보자.
‘TemplateMethodTest’
실행
logic1(), logic2() 를 호출하는 단순한 테스트 코드이다.
시간을 측정하는 부분과 비즈니스 로직을 실행하는 부분이 함께 존재한다.
이제 템플릿 메서드 패턴을 사용해서 변하는 부분과 변하지 않는 부분을 분리한다.
템플릿 메서드 패턴 - 예제2
템플릿 메서드 패턴 구조 그림

직접 만들어 보는 것
템플릿 메서드 패턴 : 디자인 패턴
추상 템플릿 : 변하는 부분을 넣는다.
오버라이드로 만드는 것
템플릿 메서드 패턴은 이름 그대로 템플릿을 사용하는 방식이다. 템플릿은 기준이 되는 거대한 틀이다. 템플릿이라는 틀에 반하지 않는 부분을 몰아둔다. 그리고 일부 변하는 부분을 별도로 호출해서 해결한다.
AbstractTemplate코드를 보자. 변하지 않는 부분인 시간 측정 로직을 몰아둔 것을 확인할 수 있따. 이제 이것이 하나의 템플릿이 된다. 그리고 템플릿 안에서 변하는 부분은 ‘call()’ 메서드를 호출해서 처리한다.템플릿 메서드 패턴은 부모 클래스에 변하지 않는 템플릿 코드를 둔다. 그리고 변하는 부분은 자식 클래스에 두고 상속과 오버라이딩을 사용해서 처리한다.SubClassLogic1:주의 - 테스트 코드(src/test)에 위치한다.변하는 부분인 비즈니스 로직1을 처리하는 자식 클래스이다. 템플릿이 호출하는 대상인
call()메서드를 오버라이딩 한다.SubClassLogic2주의 - 테스트 코드(src/test)에 위치한다.변하는 부분인 비즈니스 로직2을 처리하는 자식 클래스이다. 템플릿이 호출하는 대상인
call()메서드를 오버라이딩 한다.TemplateMethodTest실행
실행
execute() 실행하다가 call()을 호출하면 오버라이딩된 call이 호출된다.
SubClassLogic1이 실행되서 인스턴스의 메서드인 SubClassLogic1.call()메서드가 호출된다.
template1.execute()를 호출하면 템플릿 로직인AbstructTemplate.execute()를 실행한다.여기서 중간에
call()메서드를 호출하는데 이 부분이 오버라이딩 되어있다.따라서 현재 인스턴스인
SubClassLogic1인스턴스의SubClassLogic1.call()메서드가 호출된다.템플릿 메서드 패턴은 이렇게 다형성을 사용해서 변하는 부분과 변하지 않는 부분을 분리하는 방법이다.
템플릿 메서드 패턴 - 예제3
익명 내부 클래스 사용하기
템플릿 메서드 패턴은
SubClassLogic1,SubClassLogic2처럼 클래스를 계속 만들어야 하는 단점이 있다. 익명 내부 클래스를 사용하면 이런 단점을 보완할 수 있다.익명 내부 클래스를 사용하면 객체 인스턴스를 생성하면서 동시에 생성할 클래스를 상속 받은 자식 클래스를 정의할 수 있다. 이 클래스는
SubClassLogic1처럼 직접 지정하는 이름이 없고 클래스 내부에 선언되는 클래스여서 익명 내부 클래스라고 한다.익명 내부 클래스에 대한 자세한 내용은 자바 기본 문법을 참고하자.
추상클래스를 상속받은 로직을 바로 구현하는 것
실행
실행
TemplateMethodTest$1,TemplateMethodTest$2이다.템플릿 메서드 패턴 - 적용1
우리가 만든 템플릿 메서드에 추상 템플릿 적용
abstract T call()은 변하는 부분을 처리하는 메서드이다. 이 부분은 상속으로 구현해야 한다.v3 → v4 복사
실행
AbstractTemplate<String>익명 내부 클래스
template.execute("OrderController.request()")OrderServiceV4AbstractTemplate<Void> template = new AbstractTemplate<>(trace) { @Override protected Void call() { orderRepository.save(itemId); return null; } }; template.execute("OrderService.orderItem()"); //ctrl + alt + N정상 실행 로그
템플릿 메서드 패턴 - 적용2
템플릿 메서드 패턴 덕분에 변하는 코드와 변하지 않는 코드를 명확하게 분리했다.
로그를 출력하는 템플릿 역할을 하는 변하지 않는 코드는 모두
AbstractTemplate에 담아두고 변하는 코드는 자식 클래스를 만들어서 분리했다.좋은 설계란
좋은 설계란 무엇일까 수많은 멋진 정의가 있겠지만, 진정한 좋은 설계는 바로 변깅이 일어날때 자연스럽게 드러난다.
지금까지 로그를 남기는 부분을 모아서 하나로 모듈화하고 비즈니스 로직 부분을 분리했다. 여기서 만약 로그를 남기는 로직을 변경해야 한다고 생각해보자. 그래서
AbstractTemplate코드를 변경해야 한다고 가정해보자. 단순히AbstractTemplate코드만 변경하면 도니다.템플릿이 없는 V3 상태에서 로그를 남기는 로직을 변경해야 한다고 생각해보자. 이 경우 모든 클래스를 다 찾아서 고쳐야 한다. 클래스가 수백개라면 생각만 해도 끔찍하다.
단일 책임 원칙(SRP)
v4는 단순히 템플릿 메서드 패턴을 적용해서 소스코드 몇줄을 줄인 것이 전부가 아니다.
로그를 남기는 부분에 단일 책임 원칙을 지킨 것이다. 변경 지점을 하나로 모아서 변경에 쉽게 대처할 수 있는 구조를 만든것이다.
템플릿 메서드 패턴 - 정의
GOF 디자인 패턴에서는 템플릿 메서드 패턴을 다음과 같이 정의했다.
GOF 템플릿 메서드 패턴 정의
풀어서 설명하면 다음과 같다.
부모 클래스에 알고리즘의 골격인 템플릿을 정의하고 일부 변경되는로직은 자식 클래스에 정의하는 것이다. 이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고 특정 부분만 재정의할 수 있다. 결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.
하지만
템플릿 메서드 패턴은 상속을 사용한다. 따라서 상속에서 오는 단점들을 그대로 안고간다. 특히 자식 클래스가 부모클래스와 컴파일 시점에 강하게 결합되는 문제가 있다. 이것은 의존관계에 대한 문제이다. 자식 클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는다.
이번 장에서 지금까지 작성했던 코드를 떠올려보자. 자식 클래스를 작성할때 부모 클래스의 기능을 사용한 것이 있었던가?
그럼에도 불구하고 템플릿 메서드 패턴을 위해 자식 클래스는 부모 클래스를 상속 받고 있다.
상속을 받는다는 것은 특정 부모 클래스를 의존하고 있다는 것이다. 자식 클래스의 extends 다음에 바로 부모 클래스가 코드상에 지정되어 있다. 따라서 부모 클래스의 기능을 사용하든 사용하지 않는 간에 부모 클래서를 강하게 의존하게 된다. 여기서 강하게 의존한다는 뜻은 자식 클래스의 코드에 부모 클래스의 코드가 명확하게 적혀있다는 뜻이다.
UML 에서 상속을 받으면 삼각형 화살표가 자식 → 부모 를 향하고 있는 것은 이런 의존관계를 반영하는 것이다. 자식 클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는데 부모 클래스를 알아야한다. 이것은 좋은 설계가 아니다. 그리고 이런 잘못된 의존관계 때문에 부모 클래스를 수정하면, 자식 클래스에도 영향을 줄 수 있다.
추가로 템플릿 메서드 패턴은 상속 구조를 사용하기 때문에 별도의 클래스나 익명 내부 클래스를 만들어야 하는 부분도 복잡하다.
지금까지 설명한 이런 부분들을 더 깔끔하게 개선하려면 어떻게 해야할까?
템플릿 메서드 패턴과 비슷한 역할을 하면서 상속의 단점을 제거할 수 있는 디자인 패턴이 바로 전략 패턴이다(Strategy Pattern).
All reactions