Open cme10575 opened 1 year ago
@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={}", template2.getClass());
template2.execute();
}
TemplateMethodTest$1
, TemplateMethodTest$2
이런 식으로 자바에서 임의로 만들어준다.AbstractTemplate
은 템플릿 메서드 패턴에서 부모 클래스이고, 템플릿 역할을 한다.<T>
제네릭을 사용했다. 반환 타입을 정의한다.LogTrace trace
를 전달 받는다.call()
메서드를 통해서 변하는 부분을 처리한다.abstract T call()
은 변하는 부분을 처리하는 메서드이다. 이 부분은 상속으로 구현해야 한다.AbstractTemplate<Void>
Void
타입을 사용하고 null 을 반환하면 된다. 참고로 제네릭은 기본 타입인 void
, int
등을 선언할 수 없다.AbstractTemplate
만 수정하면 된다.템플릿 메서드 디자인 패턴의 목적은 다음과 같습니다.
"작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다. 템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있습니다." [GOF]
- 이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고, 특정 부분만 재정의할 수 있다. 결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.
- 하지만 (- 템플릿 메서트 패턴의 단점)
- 템플릿 메서드 패턴은 상속을 사용한다. 따라서 상속에서 오는 단점들을 그대로 안고간다. 특히 자식 클래스가 부모 클래스와 컴파일 시점에 강하게 결합(의존 관계가 생성)되는 문제가 있다.
- 강하게 의존한다는 뜻은 자식 클래스의 코드에 부모 클래스의 코드가 명확하기 적혀 있다는 것이다.
- 이번 코드에서 자식 클래스는 부모 클래스의 기능을 하나도 사용하고 있지 않지만, 템플릿 메서드 패턴을 위해 부모 클래스를 상속받고 있다.
- 자식 클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는데, 부모 클래스를 알아야 한다. 이것은 좋은 설계가 아니다. 그리고 이런 잘못된 의존관계 때문에 부모 클래스를 수정하면, 자식 클래스에도 영향을 줄 수 있다. (ex. 부모 클래스에 메서드를 하나 더 생성하면, 자식 클래스들은 전부 그 메서드를 오버라이드 해야한다.)
- 템플릿 메서드 패턴과 비슷한 역할을 하면서 상속의 단점을 제거할 수 있는 디자인 패턴이 바로 전략 패턴(Strategy Pattern)이다.
핵심기능(변하는 부분)과 부가기능(변하지 않는 부분)을 분리하는 패턴
구현 방법
예시
@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();
}
단점
=> 익명 내부 클래스를 사용하면 보완 가능
@Test
void templateMethodV2() {
AbstractTemplate template1 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
};
template1.execute();
AbstractTemplate template2 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직2 실행");
}
};
template2.execute();
}
=> 전략 패턴을 사용해 상속의 단점을 제거
장점
구현 방법
예시
@Slf4j
public class ContextV1 {
private Strategy strategy;
public ContextV1(Strategy strategy) {
this.strategy = strategy;
}
public void execute() {
long startTime = System.currentTimeMillis();
// 비즈니스 로직 실행
strategy.call();
// 비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
}
public interface Strategy {
void call();
}
장점
단점
=> 메소드의 파라미터로 Strategy를 받으면 Context를 실행할 때마다 Strategy를 파라미터로 전달한다. 원하는 Strategy를 유연하게 변경 가능
=> 단점. 실행할 때마다 전략 지정해야하는게 번거롭다. 실습예제는 이 방법이 더 적합.
구현 방법
예시
public interface Callback {
void call();
}
@Slf4j
public class TimeLogTemplate {
public void execute(Callback callback) {
long startTime = System.currentTimeMillis();
// 비즈니스 로직 실행
callback.call();
// 비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
}
@Test
void callbackV1() {
TimeLogTemplate template = new TimeLogTemplate();
template.execute(() -> log.info("비즈니스 로직1 실행"));
template.execute(() -> log.info("비즈니스 로직2 실행"));
}
v4: 핵심 기능과 템플릿을 호출하는 코드가 섞여 있다. -> 변하는 것과 변하지 않는 것을 분리해서 모듈화 하는것이 중요 v4의 템플릿 메서드 패턴을 사용한 덕분에 핵심 기능에 좀 더 집중 할 수 있었다.
좋은 설계라는 것은 무엇일까? 수 많은 멋진 정의가 있겠지만, 진정한 좋은 설계는 바로 변경이 일어날 때 자연스럽게 드러난다. 지금까지 로그를 남기는 부분을 모아서 하나로 모듈화하고, 비즈니스 로직 부분을 분리했다. 여기서 만약 로그를 남기는 로직을 변경해야 한다고 생각해보자. 그래서 AbstractTemplate 코드를 변경해야 한다 가정해보자. 단순히 AbstractTemplate 코드만 변경하면 된다. 템플릿이 없는 V3 상태에서 로그를 남기는 로직을 변경해야 한다고 생각해보자. 이 경우 모든 클래스를 다 찾아서 고쳐야 한다. 클래스가 수백 개라면 생각만해도 끔찍하다.
V4 는 단순히 템플릿 메서드 패턴을 적용해서 소스코드 몇줄을 줄인 것이 전부가 아니다. 로그를 남기는 부분에 단일 책임 원칙(SRP)을 지킨 것이다. 변경 지점을 하나로 모아서 변경에 쉽게 대처할 수 있는 구조를 만든 것이다.
[20230219]
템플릿 메서드 패턴
: 변하는 것과 변하지 않는 것을 분리
한계
참고) 리턴값 설정 제네릭을 통해서 해결가능함
참고) 익명 내부클래스
질문
타입 설정은 클래스단위인데, execute 리턴은 메서드 단위임 두 개 이상의 메서드가 있고 각각 다른 타입을 리턴하고 싶으면 어떡하는지? AbstractTemplate<T, A> 이렇게 받아 메서드마다 설정하기엔 직관적이지 않은 듯... 실제로 쓰이는지?
UML 잘 모르는데 메서드 앞 +, # 어떤 의미인지?
[20230226]
되돌아보는 우리의 목적
전략 패턴
한계
템플릿 콜백 패턴
템플릿
콜백
템플릿 콜백 패턴
한계
기타
단축키
인라인
실행