위에서 우리는 UserDao와 JdbcContext, StatementStrategy를 이용해 코드를 짰다.
이 코드는 전략 패턴의 기본 구조에 익명 내부 클래스를 활용한 방식이고, 이런 방식을 스프링에서는 “템플릿/콜백 패턴” 이라고 부른다. 전략 패턴의 컨텍스트를 템플릿이라 부르고, 익명 내부클래스로 만들어지는 오브젝트를 콜백이라고 부른다.
콜백은 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트를 말한다.
템플릿/콜백 패턴의 콜백은 일반적으로 하나의 메소드를 가진 인터페이스를 구현한 익명 내부 클래스로 만들어진다.
템플릿/콜백 방식은 전략 패턴과 DI의 장점을 익명 내부 클래스 사용 전략과 결합한 하나의 디자인 패턴이라고 봐도 된다.
이전에 만들었던 UserDao의 작업 흐름은 위와 같다.
하지만 템플릿/콜백 방식에서 아쉬운 점은 DAO 메소드에서 매번 익명 내부 클래스를 사용하기 때문에 코드의 가독성이 떨어진다.
만약 익명 내부클래스에서 반복되어서 분리를 통해 재사용이 가능한 코드를 찾아낼 수 있다면 익명 내부 클래스를 사용한 코드를 간결하게 만들 수도 있다.
public void deleteAll() throws SQLException {
this.jdbcContext.workWithStatementStrategy(
new StatementStrategy() (
public PreparedStatement makePreparedStatement(Connection c)
throws SQLException {
return c.prepareStatement("delete from users");
}
)
);
}
위의 코드를 살펴보면 SQL 쿼리를 하나 날리는 메소드를 구현하는 것이 전부이다.
많은 콜백 오브젝트가 위와 같이 SQL 쿼리를 하나 날리는 내용일 것이다.
→ SQL문장만 파라미터로 받아서 바꿀 수 있게 하고 메소드 내용 전체를 분리해 별도의 메소드로 만들어보자.
public void deleteAll() throws SQLException {
executeSql(‘delete from users");
}
private void executeSql(final String query) throws SQLException {
this.jdbcContext.workWithStatementStrategy(
new StatementStrategy() {
public PreparedStatement makePreparedStatement(Connection c)
throws SQLException {
return c.prepareStatement(Query);
}
}
);
}
이렇게 deleteAll에서 executeSql을 분리시켜서 재사용 할 수 있게 만들었다. 하지만 executeSql() 메소드는 UserDao만 사용하기는 아깝다.
이렇게 재사용 가능한 콜백을 담고 있는 메소드라면 DAO가 공유할 수 있는 템플릿 클래스 안으로 옮겨도 된다.
executeSql()를 JdbcContext로 옮기면 아래와 같이 모든 DAO 메소드에서 executeSql() 메소드를 사용할 수 있게 된다.
public void deleteAll() throws SQLException {
this.jdbcContext.executeSql("delete from users");
}
자바 언어에 타입 파라미터라는 개념을 도입한 제네릭스를 활용하면 콜백 결과의 타입을 다양하게 설정 할 수 있다.
파일의 각 라인에 있는 문자를 모두 연결해서 문자열로 돌려주는 기능과 파일의 각 라인에 있는 숫자들의 곱을 정수로 돌려주는 기능을 개발한다고 하면 반환형이 각각 문자열과 정수여야 한다.
그런 경우, 제네릭스를 이용한 콜백 인터페이스를 사용하면 된다.
public interface LineCallback<T> {
T doSomethingWithLine (String line , T value);
}
위와 같이 타입을 <>에 받을 수 있는 LineCallback 인터페이스를 정의하고
public <T> T lineReadTemplate(String filepath , LineCallback<T> callback, T initVal)
throws IOException {
BufferedReader br =null;
try {
br =new BufferedReader(new FileReader(filepath));
T res =initVal;
String line =null;
while((line = br.readLine()) != null) {
res = callback.doSomethingWithLine(line, res);
}
return res;
}
catch(IOException e) {...}
finally {...}
}
T타입을 반환하는 lineReadTemplate 메소드를 정의하고 파라미터로 아까 선언한 제네릭 인터페이스 LineCallback 타입을 받는다.
그리고, 메소드 안에서 callback 오브젝트의 메소드를 호출한다.
public String concatenate(String filepath) throws IOException {
LineCallback<String> concatenateCallback =
new LineCallback<String>() {
public String doSomethingWithLine(String line, String value) {
return value + line;
}
};
return lineReadTemplate(filepath, concatènateCallback, " “ );
}
그리고 위와 같이 문자열 연결 기능 콜백을 이용해 메소드를 만들었다.
또한 스프링은 JDBC 코드 작성을 위해 다양한 템플릿과 콜백을 제공한다(JdbcTemplate).