Spring 템플릿 메서드 패턴, 콜백 패턴

개발하는개발자·2022년 11월 28일
0

Spring

목록 보기
5/6

김영한씨의 Spring Core 핵심 원리 공부 기록

변하는 것과 변하지 않는 것을 분리

좋은 설계는 변하는 것과 변하지 않는 것을 분리하는 것이다.
여기서 핵심 기능 부분은 변하고, 로그 추적기를 사용하는 부분은 변하지 않는 부분이다.
이 둘을 분리해서 모듈화해야 한다.
템플릿 메서드 패턴(Template Method Pattern)은 이런 문제를 해결하는 디자인 패턴이다.

템플릿 메서드 패턴

@Slf4j
public class SubClassLogic1 extends AbstractTemplate {
 @Override
 protected void call() {
 log.info("비즈니스 로직1 실행");
 }
}

@Slf4j
public class SubClassLogic2 extends AbstractTemplate {
 @Override
 protected void call() {
 log.info("비즈니스 로직2 실행");
 }
}

@Test
void templateMethodV1() {
 AbstractTemplate template1 = new SubClassLogic1();
 template1.execute();
 AbstractTemplate template2 = new SubClassLogic2();
 template2.execute();
}


template1.execute() 를 호출하면 템플릿 로직인 AbstractTemplate.execute() 를 실행한다. 여기서
중간에 call() 메서드를 호출하는데, 이 부분이 오버라이딩 되어있다. 따라서 현재 인스턴스인 SubClassLogic1 인스턴스의 SubClassLogic1.call() 메서드가 호출된다.
템플릿 메서드 패턴은 이렇게 다형성을 사용해서 변하는 부분과 변하지 않는 부분을 분리하는 방법이다.

익명 내부 클래스를 활용한 같은 방법

@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();
}

템플릿 메서드 패턴 - 정의


부모 클래스에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는
것이다. 이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고, 특정 부분만 재정의할 수 있다.
결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것이다

하지만 템플릿 메서드 패턴은 상속을 사용하기 때문에 상속에서 오는 단점을 그대로 안고간다. 부모 클래스의 기능을 사용하든 하지 않든 간에 부모 클래스를 강하게 의존하게 된다. 템플릿 메서드 패턴과 비슷한 역할을 하면서 상속의 단점을 제거할 수 있는 디자인 패턴인 전략 패턴을 이용한다.

전략 패턴1


전략 패턴은 변하지 않는 부분을 Context 라는 곳에 두고, 변하는 부분을 Strategy 라는 인터페이스를 만들고 해당 인터페이스를 구현하도록 해서 문제를 해결한다. 상속이 아니라 위임으로 문제를 해결하는 것이다.
전략 패턴에서 Context 는 변하지 않는 템플릿 역할을 하고, Strategy 는 변하는 알고리즘 역할을 한다

@Slf4j
public class StrategyLogic1 implements Strategy {
 @Override
 public void call() {
 log.info("비즈니스 로직1 실행");
 }
}

@Slf4j
public class StrategyLogic2 implements Strategy {
 @Override
 public void call() {
 log.info("비즈니스 로직2 실행");
 }
}

@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);
 }
}

ContextV1 은 변하지 않는 로직을 가지고 있는 템플릿 역할을 하는 코드이다. 전략 패턴에서는 이것을 컨텍스트(문맥)이라 한다.
쉽게 이야기해서 컨텍스트(문맥)는 크게 변하지 않지만, 그 문맥 속에서 strategy 를 통해 일부 전략이 변경된다 생각하면 된다.
Context 는 내부에 Strategy strategy 필드를 가지고 있다. 이 필드에 변하는 부분인 Strategy의 구현체를 주입하면 된다.
전략 패턴의 핵심은 Context 는 Strategy 인터페이스에만 의존한다는 점이다. 덕분에 Strategy의 구현체를 변경하거나 새로 만들어도 Context 코드에는 영향을 주지 않는다.
어디서 많이 본 코드 같지 않은가? 그렇다. 바로 스프링에서 의존관계 주입에서 사용하는 방식이 바로 전략 패턴이다.

/**
 * 전략 패턴 적용
 */
@Test
void strategyV1() {
 Strategy strategyLogic1 = new StrategyLogic1();
 ContextV1 context1 = new ContextV1(strategyLogic1);
 context1.execute();
 Strategy strategyLogic2 = new StrategyLogic2();
 ContextV1 context2 = new ContextV1(strategyLogic2);
 context2.execute();
}

전략 패턴2

/**
 * 전략을 파라미터로 전달 받는 방식
 */
@Slf4j
public class ContextV2 {
 public void execute(Strategy strategy) {
 long startTime = System.currentTimeMillis();
 //비즈니스 로직 실행
 strategy.call(); //위임
 //비즈니스 로직 종료
 long endTime = System.currentTimeMillis();
 long resultTime = endTime - startTime;
 log.info("resultTime={}", resultTime);
 }
}

ContextV2 는 전략을 필드로 가지지 않는다. 대신에 전략을 execute(..) 가 호출될 때 마다 항상 파라미터로 전달 받는다.
Context 와 Strategy 를 '선 조립 후 실행'하는 방식이 아니라 Context 를 실행할 때 마다 전략을 인수로 전달한다.
클라이언트는 Context 를 실행하는 시점에 원하는 Strategy 를 전달할 수 있다. 따라서 이전 방식과 비교해서 원하는 전략을 더욱 유연하게 변경할 수 있다.

콜백 정의
프로그래밍에서 콜백(callback) 또는 콜애프터 함수(call-after function)는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는 이 콜백을 필요에 따라 즉시 실행할 수도 있고, 아니면 나중에 실행할 수도 있다.

-ContextV2 예제에서 콜백은 Strategy 이다.

-여기에서는 클라이언트에서 직접 Strategy 를 실행하는 것이 아니라, 클라이언트가 ContextV2.execute(..) 를 실행할 때 Strategy 를 넘겨주고, ContextV2 뒤에서 Strategy 가 실행된다.

템플릿 콜백 패턴

-스프링에서는 ContextV2 와 같은 방식의 전략 패턴을 템플릿 콜백 패턴이라 한다. 전략 패턴에서 Context 가 템플릿 역할을 하고, Strategy 부분이 콜백으로 넘어온다 생각하면 된다.

-스프링에서는 JdbcTemplate , RestTemplate , TransactionTemplate , RedisTemplate 처럼 다양한 템플릿 콜백 패턴이 사용된다.

profile
하루에 하나씩 배우자

0개의 댓글