728x90
템플릿 콜백 패턴이란?
템플릿 콜백 패턴은 전략 패턴의 변형으로, 개발의 유연성과 재사용성을 높이는 디자인 패턴이다.
특히, Java에서 비동기 프로그래밍이나 이벤트 기반 프로그래밍을 구현할 때 유용하게 사용된다.
Spring의 JdbcTemplate, RedisTemplate, TransactionTemplate 등에서 템플릿 콜백 패턴이 사용되고 있다.
특징
- 변하지 않는 기능(템플릿)과 변하는 기능(콜백)을 분리한다.
- 전략 패턴과 유사하지만 콜백을 템플릿 메서드의 파라미터로 전달한다.
- 콜백은 주로 단일 메소드를 가진 인터페이스(함수형 인터페이스)를 사용한다.
- 익명 클래스나 람다 표현식을 사용해서 콜백을 전달할 수 있다.
패턴 흐름
- Client가 템플릿 메서드를 호출하여 콜백을 전달한다.
- 템플릿 메서드는 정해진 흐름(workflow)에 따라 작업을 시작한다.
- 템플릿 내부에서 변경이 필요한 부분에서 콜백 객체의 메서드를 호출한다.
- 콜백 메서드가 실행되어 특정 작업을 수행한다.
- 콜백 메서드의 결과를 템플릿에 반환한다.
- 템플릿은 나머지 작업을 마무리하고 최종 결과를 클라이언트에 반환한다.
코드
// 콜백
interface Callback {
// 변하는 작업
int execute(int n);
}
// 템플릿
class Template {
// 변하지 않는 작업
int workflow(Callback callback) {
System.out.println("Workflow 시작");
int num = 100;
int result = callback.execute(num);
return result;
}
}
// 클라이언트
public class Client {
public static void main(String[] args) {
Template template = new Template();
// 콜백을 익명 클래스로 전달
int result = template.workflow(new Callback() {
@Override
public int execute(int n) {
return n * n;
}
});
System.out.println(result); // 출력: 10000
// 콜백을 람다 표현식으로 전달
result = template.workflow(n -> n + 50);
System.out.println(result); // 출력: 150
}
}
위와 같이 콜백을 익명 클래스 또는 람다 표현식으로 전달해서 목적에 맞게 유연하게 개발 할 수 있다.
JdbcTemplate을 실제 개발에서 어떻게 사용되었는지 간단하게 알아보자
실제 개발 예시 (JdbcTemplate)
JdbcTemplate은 DB작업을 간편하게 처리해주는 클래스이다.
JdbcTemplate가 DB연결, 리소스 관리, 예외 처리 등의 반복적인 작업을 대신해주기 때문에 우리 개발자들은 SQL 쿼리와 쿼리 결과에 집중할 수 있다.
JdbctTemplate의 템플릿 콜백 패턴
JdbcTemplate은 DB 연결 및 자원 관리를 담당해준다. 이 기능은 모든 JDBC 작업에 공통적으로 필요하기 때문에 변하지 않는 기능이므로 템플릿 역할을 한다.
개발자는 SQL 쿼리와 결과 처리를 위한 콜백을 구현해야 한다. 예를 들어 RowMapper 메서드는 결과 집합을 객체로 변환한다.
코드
// 콜백
public interface RowMapper<T> {
@Nullable
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
// 템플릿
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
...
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return (List)result((List)this.query((String)sql, (ResultSetExtractor)(new RowMapperResultSetExtractor(rowMapper))));
}
@Nullable
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
// Workflow
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (this.logger.isDebugEnabled()) {
this.logger.debug("Executing SQL query [" + sql + "]");
}
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
QueryStatementCallback() {
}
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
Object var3;
try {
rs = stmt.executeQuery(sql);
var3 = rse.extractData(rs);
} finally {
JdbcUtils.closeResultSet(rs);
}
return var3;
}
public String getSql() {
return sql;
}
}
return this.execute(new QueryStatementCallback(), true);
}
private static <T> T result(@Nullable T result) {
Assert.state(result != null, "No result");
return result;
}
...
}
public class RowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T>> {
private final RowMapper<T> rowMapper;
private final int rowsExpected;
public RowMapperResultSetExtractor(RowMapper<T> rowMapper) {
this(rowMapper, 0);
}
public RowMapperResultSetExtractor(RowMapper<T> rowMapper, int rowsExpected) {
Assert.notNull(rowMapper, "RowMapper must not be null");
this.rowMapper = rowMapper;
this.rowsExpected = rowsExpected;
}
public List<T> extractData(ResultSet rs) throws SQLException {
List<T> results = this.rowsExpected > 0 ? new ArrayList(this.rowsExpected) : new ArrayList();
int rowNum = 0;
while(rs.next()) {
results.add(this.rowMapper.mapRow(rs, rowNum++));
}
return results;
}
}
// 클라이언트
public class Client {
public static void main(String[] args) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.query("SELECT * FROM users", (RowMapper<User>) (rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
return user;
});
}
}
- 클라이언트는 JdbcTemplate의 query 메서드를 호출하여 SQL쿼리와 RowMapper 콜백을 전달한다.
- JdbcTemplate은 템플릿 공통 작업을 처리한다. (코드 상에는 해당 로직은 생략했다.) query 메서드는 내부적으로 RowMapper를 RowMapperResultSetExtractor에 전달하여 결과를 처리하도록 했다.
- SQL 쿼리를 실행하고, 결과(ResultSet)을 반환한다.
- RowMapperResultSetExtractor의 extractData 메서드가 호출되고 각 결과 행에 대해 RowMapper 콜백의 mapRow() 메서드가 실행된다. mapRow() 메서드는 결과 집합에서 데이터를 추출하고, 이를 User 객체로 변환한다.
- 반환된 User 객체 리스트가 클라이언트에 반환된다.
참조
728x90