템플릿-콜백 패턴이란?
1. 복잡하지만 바뀌지 않는 일정한 패턴을 갖는 작업의 흐름이 존재하고 그중 일부분만 자주 바꿔서 사용해야 하는 경우에 적합한 구조이다.
2. 전략패턴의 기본 구조에 익명 내부 클래스를 활용한 방식이다.
템플릿-콜백 패턴의 가장 전형적인 예로 try/catch/finally 블록이 적당하다. 예외상황을 처리하기 위한 catch와 리소스를 반납하거나 제거하는 finally는 자주 반복되는 상황이 발생할 수 있는데, 이때 템플릿-콜백 패턴을 적용해 볼 수 있다.
public Integer calcSum(String filePath) throws IOException {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(filePath));
Integer sum = 0;
String line = null;
while ((line = br.readLine()) != null) {
sum += Integer.valueOf(line);
}
return sum;
} catch (IOException e) {
throw e;
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
throw e;
}
}
}
}
public Integer calcMultiply(String filePath) throws IOException {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(filePath));
Integer multiply = 0;
String line = null;
while ((line = br.readLine()) != null) {
multiply *= Integer.valueOf(line);
}
return multiply;
} catch (IOException e) {
throw e;
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
throw e;
}
}
}
}
calcSum, calcMultiply 두 함수의 역할은 각각 파일을 읽어 라인별로 더한 값과 곱한 값을 구하는 함수다.
서로 하는 역할은 다르지만 try/catch/finally 블록으로 굉장히 유사한 형태의 모습을 하고 있다. 이렇게 유사한 형태의 함수에서 중복되는 소스와 중복되지 않는 소스로 분리하여 구분할 수 있는데, 변하는 값(중복되지 않는 소스)은 하나의 템플릿으로 묶고, 변하지 않는 값(중복되는 소스)는 콜백 함수로 역할을 분리할 수 있다.
우선 변하는 값을 찾아 콜백 인터페이스를 구현한다. 각 함수의 변하는 값은 다음과 같다.
sum += Integer.valueOf(line);
multiply *= Integer.valueOf(line);
변하지 않는 값은 변하는 값을 제외한 나머지 try/catch/finally 블록으로 *템플릿으로 분리 되어야 하는 코드다.
public Integer lineReadTemplate(String filePath, LineCallback callback, Integer initVal)
throws IOException {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(filePath));
Integer result = 0;
String line = null;
while ((line = br.readLine()) != null) {
result = callback.doSomethingWithLine(line, result);
}
return result;
} catch (IOException e) {
throw e;
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
throw e;
}
}
}
}
lineReadTemplate 함수에 try/catch/finally 블록을 구현하였고, 변하는 값에 대해서는 콜백 함수를 전달 받아 필요한 연산을 수행할 수 있도록 하였다.
템플릿으로 분리할 때는 템플릿이 콜백에게, 콜백이 템플릿에게 전달하는 내용인지 잘 파악해야 하고, 그에 따라 콜백의 인터페이스를 정의할 수 있다.
public interface LineCallback {
Integer doSomethingWithLine(String line, Integer value);
}
public Integer calcSum(String filePath) throws IOException {
LineCallback lineCallback =
new LineCallback<Integer>() {
@Override
public Integer doSomethingWithLine(String line, Integer value) {
return value += Integer.parseInt(line);
}
};
return lineReadTemplate(filePath, lineCallback, 0);
}
public Integer calcMultiply(String filePath) throws IOException {
LineCallback lineCallback =
new LineCallback<Integer>() {
@Override
public Integer doSomethingWithLine(String line, Integer value) {
return value *= Integer.valueOf(line);
}
};
return lineReadTemplate(filePath, lineCallback, 1);
}
LineCallback 인터페이스를 생성하여 파일의 라인별 값을 처리할 수 있는 함수를 생성했다. calcSum, calcMultiply 함수에서는 각 함수의 역할에 맞게 LineCallback 인터페이스를 구현하여 템플릿으로 파라마티를 넘겨준다.
이렇게 하면 변하는 값과 변하지 않는 값을 기준으로 각각 관심사를 분리할 수 있게 된다.
try/catch/finally 블록으로 간단한 템플릿-콜백 패턴을 알아봤는데, 코드의 특성이 바뀌는 경계를 잘 살피고 그것을 인터페이스를 사용해 분리한다는 개념으로 접근할 수 있다.
[참고자료]
토비의 스프링 3.1 (Vol.1 스프링 이해와 원리)