[Spring] 템플릿 메서드 패턴과 콜백 패턴

donghyeok·2023년 2월 19일
0

Spring

목록 보기
2/9

1. 템플릿 메서드 패턴 (Template Method Pattern)

템플릿 메서드 패턴은 이름 그대로 템플릿을 사용하는 방식이다. 템플릿은 기준이 되는 거대한 틀이다.
템플릿이라는 틀에 변하지 않는 부분을 몰아둔다. 그리고 일부 변하는 부분을 별도로 호출해서 해결한다.

위 그림에서는 변하지 않는 로직들을 excute()에 몰아두고 자식 클래스에서 변하는 로직인 call()을 오버라이딩하여 구현하여 처리한다.

템플릿 메서드 패턴은 이렇게 다형성을 이용하여 변하는 부분변하지 않는 부분을 분리하는 방법이다.

단일 책임 원칙 SRP (Single Responsibility Principle)

단일 책임 원칙이란 객체는 단 하나의 책임만을 가져야한다라는 원칙이다.
단일 책임 원칙 준수 유무의 가장 큰 척도는 코드 변경이 일어날 때의 파급효과이다.
위 설계에서 변하지 않는 부분을 변경한다고 가정해보자 위와 같이 설계 했을 경우 template 역할을 하는 추상 클래스 내부 로직만 변경하면 된다. (잘못 설계된 경우 무수히 퍼져있는 동일 로직을 변경해야하는 참사 발생)

예시 (익명 내부 클래스, 람다 사용)

위 예시 그대로 코드로 구현해 본다.

@Sl4j
public abstract class AbstractTemplate {

	//변하지 않는 부분
    //시간 측정하는 로직
	public void execute() {
    	long start = System.currentTimeMillis();
       	//변하는 로직 호출
        call();
        long end = System.currenttimeMillis();
        log.info("resultTime = {}ms", end - start);
    }
    
    //변하는 부분 
    //자식 클래스에서 오버라이딩하여 사용
    protected abstract void call();
}

위 추상 클래스를 활용하려면 자식 클래스를 계속 정의해줘야한다.
이러한 불편함 해소를 위해 익명 내부 클래스 및 람다식(인터페이스, 추상 클래스의 메서드가 1개인 경우)을 사용할 수 있다.

@Test
void templateTest() {
	//방식1 익명 내부 클래스 사용 
	AbstractTemplate template1 = new AbstractTemplate() {
    	@Override
        protected void call() {
        	//내부 비즈니스 로직 수행 
        }
    }
    template1.execute();
    
    //방식2 람다식 사용 
    AbstractTemplate template2 = () -> { //내부 비즈니스 로직 수행 }
    template2.execute();
}

문제점

얼핏 보면 템플릿 메서드 패턴을 단일 책임 원칙을 준수하는 만능 패턴처럼 보이나, 단점도 존재한다.
템플릿 메서드 패턴은 상속을 기반으로 하는데, 상속의 경우 자식 클래스는 부모 클래스의 기능을 전혀 사용하지 않는데 부모 클래스를 명시해야 한다.
이는 컴파일 시점에 자식 클래스가 부모 클래스에 강하게 결합한다는 뜻이고 이는 좋은 설계가 아니다.
또한 위 예시처럼 상속으로 인해 별도의 클래스나 익명 내부 클래스를 만드는 부분도 복잡하다.
템플릿 메서드 패턴과 비슷한 역할을 하면서 상속의 단점을 없앤 패턴이 있는데 바로 전략 패턴(Strategy Pattern)이다.

2. 전략 패턴 (Strategy Pattern)

템플릿 메서드 패턴은 부모 클래스에 변하지 않는 부분을 두고, 변하는 부분을 자식 클래스에 두어서 상속을 사용해서 문제를 해결했다.
전략 패턴은 변하지 않는 부분Context라는 곳에 두고, 변하는 부분Strategy라는 인터페이스를 만들어 해당 인터페이스를 구현하도록 문제를 해결했다.
상속이 아닌 위임으로 문제를 해결하는 것이다.
전략 패턴에서 Context는 변하지 않는 템플릿 역할을 하고, Strategy는 변하는 알고리즘 역할을 한다.
Spring에서 의존관계 주입에 사용하는 패턴이 바로 전략 패턴이다.

예시

템플릿 메서드 패턴 예시를 그대로 전략 패턴으로 구현해본다.


public interface Strategy {
	void call();
}

@Slf4j
public class Context {
	private Strategy strategy;
    
    public Context(Strategy strategy) {
    	this.strategy = strategy;
    }
    
    //변하지 않는 부분 
    //시간 측정하는 로직 
    public void execute() {
    	long start = System.currentTimeMillis();
       	//변하는 로직 호출
        strategy.call();
        long end = System.currenttimeMillis();
        log.info("resultTime = {}ms", end - start);
    }
}
@Test
void templateTest() {
	Context context1 = new Context(() -> //내부 비즈니스 로직1 실행 );
    context1.execute();
    Context context2 = new Context(() -> //내부 비즈니스 로직2 실행 );
    context2.execute();
}

위 코드는 선 조립, 후 실행 방식을 취하고 있다.
위 방식은 실행하는 시점에는 이미 조립이 끝났기 때문에 전략을 신경쓰지 않고 단순히 실행하면 된다.
단점은, 조립 이후에 전략을 변경하기가 번거롭다는 점이다.
이러한 경우 필드 지정이 아닌 파라미터로 전달하는 방식으로 구현하면 실행시 마다 전략을 유연하게 변경할 수 있다. (템플릿 콜백 패턴)

3. 템플릿 콜백 패턴

콜백이란 전략 패턴에서 파라미터로 Strategy를 넘기듯이 다른 코드의 인수로서 넘겨주는 실행가능한 코드를 콜백이라고 한다.
템플릿 콜백 패턴은 GOF 패턴은 아니고 스프링 내부 패턴에서 부르는 패턴이다.
전략 패턴에서 템플릿과 콜백 부분이 강조된 패턴이라고 보면 편리하다.
스프링에서 XxxTemplate이름 형식이라면 템플릿 콜백 패턴으로 만들어져 있다고 생각하면 된다.

예시


public interface Callback {
	void call();
}

@Slf4j
public class TimeLogTemplate {

    //변하지 않는 부분 
    //시간 측정하는 로직 
    public void execute(Callback callback) {
    	long start = System.currentTimeMillis();
       	//변하는 로직 호출
        callback.call();
        long end = System.currenttimeMillis();
        log.info("resultTime = {}ms", end - start);
    }
}
@Test
void templateTest() {
	TimeLogTemplate template = new TimeLogTemplate();
    template.execute(() -> //내부 비즈니스 로직1 실행);
    template.execute(() -> //내부 비즈니스 로직2 실행);
}

0개의 댓글