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
ContextV1은 변하지 않는 로직을 가지고 있는 템플릿 역할을 하는 코드이다. 전략 패턴에서는 이것을 컨텍스트(문맥) 이라고 한다.
쉽게 이야기해서 컨텍스트(문맥)는 크게 변하지 않지만, 그 문맥 속에서 ‘Strategy’ 를 통해 일부 전략이 변경된다 생각하면 된다.
Context 는 내부에 Strategy strategy 필드를 가지고 있다.
private Strategy strategy;
이 필드에 변하는 부분인 Strategy의 구현체를 주입하면 된다. (생성자를 통해 의존관계 주입을 하고 있다) 전략 패턴의 핵심은 Context는 Strategy 인터페이스에만 의존한다는 점이다. 덕분에 Strategy 의 구현체를 변경하거나 새로 만들어도 Context 코드에는 영향을 주지 않는다.
지금까지 일반적으로 이야기하는 전략패턴에 대해서 알아보았다. 변하지 않는 부분을 Context에 두고 변하는 부분을 Strategy 를 구현해서 만든다.
그리고 Context의 내부 필드에 Strategy 를 주입해서 사용했다.
선 조립 후 실행
여기서 이야기하고 싶은 부분은 Context 의 내부 필드에 Strategy 를 두고 사용하는 부분이다.
이 방식은 Context 와 Strategy를 실행하기 전에 원하는 모양으로 조립해두고 그 다음에 Context를 실행하는 선 조립 후 실행 방식에서 매우 유용하다.
Context와 Strategy를 한번 죕하고 나면 이후로는 Context를 실행하기만 하면 된다. 우리가 스프링으로 애플리케이션을 개발할때 애플리케이션 로딩 시점에 의존관계 주입을 통해 필요한 의존관계를 모두 맺어두고 난 다음에 실제 요청을 처리하는 것과 같은 원리이다.
이 방식의 단점은 Context와 Strategy를 조립한 이후에는 전략을 변경하기가 번거롭다는점이다. Context 에 setter를 제공해서 Strategy를 넘겨받아 변경하면 되지만, Context 를 싱글톤으로 사용할때는 동시성 이슈 등 고려할 점이 많다.
그래서 전략을 실시간으로 변경해야하면 차라리 이전에 개발한 테스트 코드처럼 Context를 하나 더 생성하고 그곳에 다른 Strategy 를 주입하는 것이 더 나은 선택일 수 있다.
이렇게 먼저 조립하고 사용하는 방식보다 더 유연하게 전략패턴을 사용하는 방법은 없을까?
전략패턴 - 예제 3
Context의 필드에 Strategy 를 주입해서 사용한다.
packagehello.advanced.trace.strategy.code.strategy;
importlombok.extern.slf4j.Slf4j;
/** * 전략을 파라미터로 전달 받는 방식 */@Slf4jpublicclassContextV2 {
publicvoidexecute(Strategystrategy) {
longstartTime = System.currentTimeMillis();
strategy.call(); //위임longendTime = System.currentTimeMillis();
longresultTime = endTime - startTime;
log.info("resultTime ={}", resultTime);
}
}
ContextV2는 전략을 필드로 가지지 않는다. 대신에 전략을 execute()가 호출되때마다 항상 파라미터로 전달받는다.
Context와 Strategy 를 선조립 후 실행 하는 방식이 아니라 Context를 실행할때마다 전략을 인수로 전달한다. 클라이언트는 Context를 실행하는 시점에 원하는 Strategy를 전달할 수 있다. 따라서 이전 방식과 비교해서 원하는 전략을 저더욱 유연하게 변경할 수 있다.
테스트 코드를 보면 하나의 Context 만 생성한다. 그리고 하나의 Context 에 실행 시점에 여러 저략을 인수로 전달해서 유연하게 실행하는 것을 확인할 수 있다.
쉽게 이야기해서 callback은 코드가 호출(call)은 되는데 코드를 넘겨준 곳의 뒤(back)에서 실행된다는 뜻이다.
ContextV2 예제에서 콜백은 Strategy 이다.
여기에서는 클라이언트에서 직접 Strategy를 실행하는 것이 아니라 클라이언트가 ContextV2.execute()를 실행할때 Strategy를 넘겨주고 ContextV2 뒤에서 Strategy 가 실행된다.
자바 언어에서 콜백
자바 언어에서 실행 가능한 코드를 인수로 넘기려면 객체가 필요하다. 자바 8부터는 람다를 사용할 수 있다.
자바 8 이전에는 보통 하나의 메소드를 가진 인터페이스를 구현하고 주로 익명 내부 클래스를 사용했다
최근에는 주로 람다를 사용한다.
템플릿 콜백 패턴
스프링에서는 ContextV2 와 같은 방식의 전략 패턴을 템플릿 콜백 패턴이라 한다. 전략 패턴에서 Context 가 템플릿 역할을 하고 Strategy 부분이 콜백으로 넘어온다 생각하면 된다.
참고로 템플릿 콜백 패턴은 GOF 패턴 아니고 스프링 내부에서 이런 방식을 자주 사용하기 때문에 스프링 안에서만 이렇게 부른다. 전략 패턴에서 템플릿과 콜백 부분이 강조된 패턴이라 생각하면 된다.
스프링에서는 JdbcTemplate , RestTemplate , TransactionTemplate , RedisTemplate 처럼 다양한 템플릿 콜백 패턴이 사용된다. 스프링에서 이름에 XxxTemplate 가 있다면 템플릿 콜백 패턴으로 만들어져 있다 생각하면 된다.
템플릿 메서드 패턴, 전략 패턴, 그리고 템플릿 콜백 패턴까지 진행하면서 변하는 코드와 변하지 않는 코드를 분리했다. 그리고 최종적으로 템플릿 콜백 패턴을 적용하고 콜백으로 람다를 사용해서 코드 사용도 최소화 할 수 있었다.
한계
그런데 지금까지 설명한 방식의 한계는 아무리 최적화를 해도 결국 로그 추적기를 적요하기 위해서 원본 코드를 수정해야 한다는 점이다. 클래스가 수백개이면 수백개를 더 힘들게 수정하는가 조금 덜 힘들게 수정하는가의 차이가 있을뿐 코드를 다 수정해야하는 본질적인것은 마찬가지이다ㅣ.
개발자의 게으름에 대한 욕심은 끝이 없다.
수많은 개발자가 이 문제에 대해서 집요하게 고민해왔고 여러가지 방향으로 해결책을 만들어왔다.
지금부터 원본 코드를 손대지 않고 로그 추적기를 적용할 수 있는 방법을 알아보자.
프록시의 개념을 이해해야 한다.
참고
지금까지 설명한 방식은 실제 스프링 안에서 많이 사용되는 방식이다. ``XxxTemplate" 을 만나면 이번에 학습한 내용을 떠올리면 된다.
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.
Uh oh!
There was an error while loading. Please reload this page.
전략패턴 - 시작
코드
Test > trace > strategy > ContextV1Test실행
전략 패턴 - 예제 1
이번에는 동일한 문제를 전략 패턴으로 사용해서 해결해본다
변하지 않는 부분은
Context로 두고 변하는 부분은Strategy라는 인터페이스를 만들고 해당 인터페이스를 구현하도록 해서 문제를 해결한다상속이 아니라 위임/구성으로 문제를 해결하는 것이다.
Strategy로 위임전략패턴에서

Context는 변하지 않는 템플릿 역할을 하고Strategy는 변하는 알고리즘 역할을 한다.GOF 디자인 패턴에서 정의한 전략 패턴의 의도는 다음과 같다
trace > strategy > code > strategyStrategyLogic1변하는 알고리즘은
Strategy인터페이스를 구현하면 된다 여기서는 비즈니스 로직1을 구현했다.logic2
trace > strategyContextV1은 변하지 않는 로직을 가지고 있는 템플릿 역할을 하는 코드이다. 전략 패턴에서는 이것을 컨텍스트(문맥) 이라고 한다.쉽게 이야기해서 컨텍스트(문맥)는 크게 변하지 않지만, 그 문맥 속에서 ‘Strategy’ 를 통해 일부 전략이 변경된다 생각하면 된다.
Context는 내부에Strategy strategy필드를 가지고 있다.private Strategy strategy;이 필드에 변하는 부분인
Strategy의 구현체를 주입하면 된다. (생성자를 통해 의존관계 주입을 하고 있다) 전략 패턴의 핵심은Context는Strategy인터페이스에만 의존한다는 점이다. 덕분에Strategy의 구현체를 변경하거나 새로 만들어도Context코드에는 영향을 주지 않는다.스프링에서 의존관계 주입에서 사용하는 방식이 이 전략 패턴이다.
테스트 코드 작성 - strategyV1 (전략패턴)
코드를 보면 의존관계 주입을 통해
ContextV1에Strategy의 구현체인strategyLogic1을 주입하는 것을 확인할 수 있다.이렇게해서
Context안에 원하는 전략을 주입한다. 이렇게 원하는 모양으로 조립을 완료하고 난 다음에context1.execute()를 호출해서context를 실행한다.전략 패턴 실행 그림
Context에 원하는Strategy구현체를 주입한다.context를 실행한다.context는 context 로직을 시작한다.context로직 중간에strategy.call()호출해서 주입받은strategy로직을 실행한다.context는 나머지 로직을 실행한다.실행결과
전략 패턴 - 예제2
전략패턴도 익명 내부 클래스를 사용할 수 있다.
실행
logic2
실행
로그찍기
*log*.info("strategyLogic1={}" ,strategyLogic1.getClass());*log*.info("strategyLogic2={}" ,strategyLogic2.getClass());실행
$와 같이 익명 내부 클래스가 생성된 것을 확인할 수 있다.ContextV1Test - 추가
ctrl+alt_NCtrl+ Alt + NContextV1 contextx = new ContextV1(() -> *log*.info("비즈니스 로직 실행"));Alt + Enter정리
ContextV1에 전달해도 된다.Strategy인터페이스는 메서드가 1개만 있으므로 람다로 사용할 수 있다람다 내용은 자바 기본 문법
지금까지 일반적으로 이야기하는 전략패턴에 대해서 알아보았다. 변하지 않는 부분을
Context에 두고 변하는 부분을Strategy를 구현해서 만든다.그리고
Context의 내부 필드에Strategy를 주입해서 사용했다.선 조립 후 실행
Context의 내부 필드에Strategy를 두고 사용하는 부분이다.Context와Strategy를 실행하기 전에 원하는 모양으로 조립해두고 그 다음에Context를 실행하는 선 조립 후 실행 방식에서 매우 유용하다.Context와Strategy를 한번 죕하고 나면 이후로는Context를 실행하기만 하면 된다. 우리가 스프링으로 애플리케이션을 개발할때 애플리케이션 로딩 시점에 의존관계 주입을 통해 필요한 의존관계를 모두 맺어두고 난 다음에 실제 요청을 처리하는 것과 같은 원리이다.Context와Strategy를 조립한 이후에는 전략을 변경하기가 번거롭다는점이다.Context에setter를 제공해서Strategy를 넘겨받아 변경하면 되지만,Context를 싱글톤으로 사용할때는 동시성 이슈 등 고려할 점이 많다.Context를 하나 더 생성하고 그곳에 다른Strategy를 주입하는 것이 더 나은 선택일 수 있다.이렇게 먼저 조립하고 사용하는 방식보다 더 유연하게 전략패턴을 사용하는 방법은 없을까?
전략패턴 - 예제 3
Context의 필드에Strategy를 주입해서 사용한다.ContextV2는 전략을 필드로 가지지 않는다. 대신에 전략을execute()가 호출되때마다 항상 파라미터로 전달받는다.ctrl+alt+vContext와Strategy를선조립 후 실행하는 방식이 아니라Context를 실행할때마다 전략을 인수로 전달한다. 클라이언트는Context를 실행하는 시점에 원하는Strategy를 전달할 수 있다. 따라서 이전 방식과 비교해서 원하는 전략을 저더욱 유연하게 변경할 수 있다.Context만 생성한다. 그리고 하나의Context에 실행 시점에 여러 저략을 인수로 전달해서 유연하게 실행하는 것을 확인할 수 있다.전략 패턴 파라미터 실행 그림
Context를 실행하면서 인수로Strategy를 전달한다.Context는execute()로직을 실행한다.Context는 파라미터로 넘어온strategy.call()로직을 실행한다.Context의execute()로직이 종료된다.코드 - 전략패턴 익명 내부 클래스
코드 조각을 넘긴다고 생각하면 된다.
코드 - 전략패턴 익명 내부 클래스 : 람다
정리
ContextV1은 필드에Strategy를 저장하는 방식으로 전략패턴을 구사했다.Context를 실행하는 시점에는 이미 조립이 끝났기 때문에 신경쓰지 않고 단순히 실행만 하면 된다.ContextV2는 파라미터에Strategy를 전달하는 방식으로 전략패턴을 구사했다.템플릿
지금 우리가 해결하고 싶은 문제는 변하는 부분과 변하지 않는 부분을 분리하는 것이다.
변하지 않는 부분을 템플릿이라 하고 그 템플릿 안에서 변하는 부분에 약간 다른 코드 조각을 넘겨서 실행하는 것이 목적이다.
ContextV1,ContextV2두가지 방식 다 문제를 해결할 수 있지만 어떤 방식이 더 나아보이는가?지금 우리가 원하는 것은 애플리케이션 의존 관계를 설정하는 것처럼 선 조립, 후 실행이 아니다. 단순히 코드를 실행할때 변하지 않는 템플릿이 있고 그 템플릿 안에서 원하는 부분만 살짝 다른 코드를 실행하고 싶을 뿐이다.
따라서 우리가 고민하는 문제는 실행 시점에 유연하게 실행 코드 조각을 전달하는
ContexV2가 더 적합하다.템플릿 콜백 패턴 - 시작
ContextV2는 변하지 안흔 템플릿 역할을 한다. 그리고 변하는 부분은 파라미터로 넘어온Strategy의 코드를 실행해서 처리한다. 이렇게 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 콜백Callback이라 한다.콜백 정의
콜백 코드들
void strategyV3()void strategyV2()public class ContextV2Test>void strategyV1()쉽게 이야기해서
callback은 코드가 호출(call)은 되는데 코드를 넘겨준 곳의 뒤(back)에서 실행된다는 뜻이다.ContextV2예제에서 콜백은Strategy이다.Strategy를 실행하는 것이 아니라 클라이언트가ContextV2.execute()를 실행할때Strategy를 넘겨주고ContextV2뒤에서Strategy가 실행된다.자바 언어에서 콜백
템플릿 콜백 패턴
ContextV2와 같은 방식의 전략 패턴을 템플릿 콜백 패턴이라 한다. 전략 패턴에서Context가 템플릿 역할을 하고Strategy부분이 콜백으로 넘어온다 생각하면 된다.JdbcTemplate,RestTemplate,TransactionTemplate,RedisTemplate처럼 다양한 템플릿 콜백 패턴이 사용된다. 스프링에서 이름에XxxTemplate가 있다면 템플릿 콜백 패턴으로 만들어져 있다 생각하면 된다.템플릿 콜백 패턴 - 예제
Test 코드
실행
lambda
실행
템플릿 콜백 패턴 - 적용
TraceTemplate는 템플릿 역할을 한다.execute()를 보면 message 데이터와 콜백인TraceCallback callback을 전달받는다.<T>제네릭을 사용했다. 반환타입을 정의한다.Controller
this.template = new Template(trace):trace의존 관계 주입을 받으면서 필요한TraceTemplate템플릿을 생성한다. 참고로TraceTemplate을 처음부터 스프링 빈으로 등록하고 주입받아도 된다. 이 부분은 선택이다.template.execute(.., new TraceCallback() {...}: 템플릿을 실행하면서 콜백을 전달한다. 여기서는 콜백으로 익명 내부 클래스를 사용했다.Service
Repository
정리
템플릿 메서드 패턴, 전략 패턴, 그리고 템플릿 콜백 패턴까지 진행하면서 변하는 코드와 변하지 않는 코드를 분리했다. 그리고 최종적으로 템플릿 콜백 패턴을 적용하고 콜백으로 람다를 사용해서 코드 사용도 최소화 할 수 있었다.
한계
그런데 지금까지 설명한 방식의 한계는 아무리 최적화를 해도 결국 로그 추적기를 적요하기 위해서 원본 코드를 수정해야 한다는 점이다. 클래스가 수백개이면 수백개를 더 힘들게 수정하는가 조금 덜 힘들게 수정하는가의 차이가 있을뿐 코드를 다 수정해야하는 본질적인것은 마찬가지이다ㅣ.
개발자의 게으름에 대한 욕심은 끝이 없다.
수많은 개발자가 이 문제에 대해서 집요하게 고민해왔고 여러가지 방향으로 해결책을 만들어왔다.
지금부터 원본 코드를 손대지 않고 로그 추적기를 적용할 수 있는 방법을 알아보자.
프록시의 개념을 이해해야 한다.
참고
지금까지 설명한 방식은 실제 스프링 안에서 많이 사용되는 방식이다. ``XxxTemplate" 을 만나면 이번에 학습한 내용을 떠올리면 된다.
All reactions