템플릿
1장에서 관심이 다른 코드를 분리하고 (단일 책임), 확장과 변경에 대응할 수 있게 개선을 했었다.
객체지향 설꼐의 개방폐쇄 원칙을 따른것
이 원칙으로 코드에는 확장하려는 성질과 어떤 부분은 고정되어 변하지 않으려는 성질이 있는걸 알 수 있다.
템플릿은 변경이 거의 일어나지 않고 일정한 패턴으로 유지되는 부분을
자유롭게 변경되는 부분으로부터 독립시켜서 효과적으로 활용 할 수 있게 한다.
3장에서는 템플릿을 이용해 완성도 있는 DAO 만드는법과
스프링에 적용된 템플릿 기법을 봐보자
다시보는 초난감 DAO
UserDao 에서 여러가지 개선작업을 했지만 예외상황에 대한 처리를 안했었다.
DB 커넥션은 반드시 자원을 반납해야하기 때문에 예외처리를 꼭 해줘야 한다.
public void deleteAll() throws SQLException{
Connection c = dataSource.getConnetion();
try{
PreparedStatement ps = c.prepareStatement("delete from users");
ps.executeUpdate();
}catch (SQLException e){
throw e;
}finally{
// 자원 close
ps.close();
c.close();
}
}
예외처리에 안전한 코드가 됐지만 반복되는 코드가 엄청 생길 코드가 되버렸다.
변하는 것과 변하지 않는것
위 코드는 중복 코드가 많이 발생하게 된다. 이런 중복 문제를 어떻게 해결 할 지 봐보자
전략 패턴의 적용
상속을 통해 구현하는 템플릿 메소드 패넡보다 개방 폐쇄 원칙을 잘 지키고 확장성이 뛰어난 패턴이 전략 패턴이다.
전략패턴은 확장 시킬 변하는 부분을 별도의 클래스로 만들어서 인터페이스를 통해 위임하는 방식을 사용한다.
전략 패턴의 구조 - contextMethod 에서 일정한 구조를 가지고 있다가 전략 인터페이스를 통해
특정 확장 기능은 전략 클래스(StrategyImpl1, StrategyImpl2) 에 위임해서 확장시키는 구조다
위에 deleteAll 메소드로 예를 들면 변하지 않는 부분들이 contextMethod 가 된다.
deleteAll에는 JDBC로 DB를 업데이트하는 작업이라는 변하지 않는 context를 갖는다.
contextMethod 의 역할을 보면
- DB 커넥션 가져오기
- PreparedStatement 만들어줄 외부 기능 호출하기 -> 전략 패턴의 전략
- 전달받은 PreparedStatement 실행하기
- 예외가 발생하면 다시 메소드 밖으로 던지기
- PreparedStatement 와 Connection 적절히 닫아주기
저 위에 구조로 어떻게 전략 패턴을 써서 구현할지 봐보자
먼저 전략들을 만들 인터페이스를 생성시키고
public interface StatementStrategy{
PreparedStatement makePreparedStatement(Connection c) throws SQLException;
}
이 인터페이스를 implements 할 전략들을 생성해준다.
public class DeleteAllStatement implements StatementStrategy{
public PreparedStatement makePreparedStatement(Connection c) throws SQLException{
PreparedStatement ps = c.prepareStatement("delete from users");
return ps;
}
}
위 클래스로 만들어진 전략을 사용하는 contextMethod() 에 해당하는 deleteAll 메소드를 봐보자
//UserDao
public void deleteAll() throws SQLException {
try{
c = dataSource.getConnection();
StatementStrategy strategy = new DeleteAllStatement();
ps = strategy.makePreparedStatement(c);
ps.executeUpdate();
}catch (SQLException e){
}
근데 이렇게 구현을 하면 컨텍스트는 항상 그래도 유지가 되어야 하는데 여기선 DeleteAllStatement 가 명시적으로 박혀있다. 이걸 빼서 따로 context를 관리하게 만들어줘야한다.
이번엔 이런 구조로 만들어보자
클라이언트로부터 전략을 받아서 변경되지 않는 컨텍스트에서 실행이 되게 만들어주자
이렇게 따로 context를 생성을 해서
public void jdbcContextWithStatementStrategy(StatementStrategy stmt) throws SQLException{
Connection c = null;
PreparedStatement ps = null;
try {
c = dataSource.getConnection();
ps = stmt.makePreparedStatement(c);
} catch (SQLException e){
throw e;
} finally {
if(ps != null){try{ps.close();}catch(SQLException e)}
if(c != null){try{c.close();}catch(SQLException e)}
}
}
클라이언트로부터 context에 전략을 전달하도록 구현해준다.
public void deleteAll() throws SQLException {
StatemetnStrategy st = new DeleteAllStatement(); // 전략 클래스 생성
jdbcContextWithStatementStrategy(st); // 컨텍스트 호출한다
}
이렇게 전략 패턴의 모습을 갖출 수 있다.
JDBC 패턴의 최적화
전략과 클라이언트의 동거
이렇게 만들어진 구조에도 단점이 있다.
1. 패턴마다 구현클래스를 계속 만들어야 한다. 클래스 파일 개수가 많이 늘어난다.
2. 예를들어 User 라는 필드값이 필요하다면 전달받기 위해 생성자에서 파라미터를 받게 만들어야 하고, 인스턴스 변수도 만들어야 한다.
이 문제를 어떻게 해결 할지 알아보자
public void add(final User user) thorws SQLException {
public class AddStatement implements StatementStrategy{
public PreparedStatement makePreparedStatement(Connection c) throws SQLException{
PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values(?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
return ps;
}
}
StatementStrategy st = new AddStatement();
jdbcContextWithStatementStrategy(st);
}
메소드 내부에서 내부 클래스를 사용해서 파일을 새로 생성하지 않고 User 변수를 메소드 지역변수로 사용할수도 있어진다.
좀 더 간결하게 클래스 이름도 제거 할 수 있다.
익명 내부 클래스를 사용해서 처리한 방식
public void add(final User user) thorws SQLException {
jdbcContextWithStatementStrategy(
new StatementStrategy(){
PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values(?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
return ps;
});
}
템플릿과 콜백
전략 패턴의 컨텍스트를 템플릿 이라 하고,
클라이언트쪽에서 사용되는 익명 내부 클래스는 콜백이라 한다.
템플릿/콜백의 동작 원리
템플릿은 고정된 작업 흐름을 가진 코드를 재사용하기때문에 붙은 이름
콜백은 템플릿 안에서 호출되는 것을 목적으로 만든 객체
템플릿/콜백 작업 흐름
- 클라이언트에서 콜백을 생성하고 템플릿에 콜백을 전달하면서 시작
- 템플릿은 workflow를 시작하고 참조정보들을 전달하면서 콜백을 호출한다.
- 콜백은 작업을 수행하고 작업 결과를 템플릿에 전달
- 템플릿은 workflow 를 마무리 짓고 작업결과를 클라이언트에 보낸다.
템플릿/콜백은 클라이언트가 템플릿 메소드를 호출하면서 콜백 객체를 주입시키는 메소드 레벨에서 발생하는 DI 라고 볼 수 있다. 템플릿/콜백은 전략 패턴과 DI 의 장점을 익명 내부 클래스로 결합한 활용법이라고 할 수 있다.