템플릿
템플릿이란 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록 하는 방법입니다.
OCP - 개방 폐쇄 원칙
클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.
- 변화의 특성이 다른 부분을 구분해주고, 각각 다른 목적과 다른 이유에 의해 다른 시점에 독립적으로 변경될 수 있는 효율적인 구조를 만들어주는 것.
3.1 다시 보는 초난감 DAO
3.1.1 예외처리 기능을 갖춘 DAO
- DB 커넥션이라는 제한적인 리소스를 공유해 사용하는 서버에서 동작하는 JDBC 코드에는 반드시 지켜야 할 원칙이 있음 → 예외처리
- 예외가 발생했을 경우에도 사용한 리소스를 반환하도록 만들어야 함
public void deleteAll() throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("delete from users");
ps.executeUpdate();
ps.close();
c.close();
}
- Connection 객체와 PreparedStatement 객체의 close() 메소드가 실행되지 않아서 제대로 리소스가 반환되지 않을 수 있음. (예외 처리가 안되있을시)
- try/catch로 당장의 문제를 해결할 순 있다! 하지만..
3.2 변하는 것과 변하지 않는 것
3.2.1 JDBC try/catch/finally 코드의 문제점
- 코드가 복잡함
- 여러 메소드에 적용할 경우 에러를 잡기가 힘듦
3.2.2 분리와 재사용을 위한 디자인 패턴 적용
public void deleteAll() throws SQLException {
Connection c = null;
PreparedStatement ps = null;
try {
c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("delete from users"); // 변하는 부분
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
if (ps != null) { try { ps.close(); } catch (SQLException e) {} }
if (c != null) { try { c.close(); } catch (SQLException e) {} }
}
ps.close();
c.close();
}
메소드 추출
- 메소드 사용 자체가 재사용이 필요하고 분리된 메소드는 DAO 로직마다 새로 만들어줘야하는 이득없는 상황 발생
템플릿 메소드 패턴 적용
public class UserDaoDeleteAll extends UserDao {
protected PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement("delete from users");
return ps;
}
}
- 변하지 않는 부분은 슈퍼클래스에 두고 변하는 부분은 추상 메소드로 정의해둬서 서브클래스에서 오버라이드하여 새롭게 정의해 쓰도록 하는 것
- 메소드가 4개일 경우 서브클래스를 4개 만들어야 하는 문제 발생
- 확장구조가 이미 클래스를 설계하는 시점에서 고정됨
전략 패턴의 적용
- 변하는 부분을 별도의 클래스로 만들어 추상화된 인터페이스를 통해 위임하는 방식.
// 3-8 StatementStrategy 인터페이스
package springbook.user.dao;
...
public interface StatementStrategy {
PreparedStatement makePreparedStatement(Connection c) throws SQLException;
}
// 3-9 deleteAll() 메소드의 기능을 구현한 StatementStrategy 전략 클래스
package springbook.user.dao;
...
public class DeleteAllStatement implements StatementStrategy {
public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement("delete from users");
return ps;
}
}
// 3-10 전략 패턴을 따라 DeleteAllStatement가 적용된 deleteAll() 메소드
public void deleteAll() throws SQLException {
...
try {
c = dataSource.getConnection();
StatementStrategy strategy = new DeleteAllStatement();
ps = strategy.makePreparedStatement(c);
ps.executeUpdate();
} catch (SQLException e) {
...
}
DI 적용을 위한 클라이언트/컨텍스트 분리
- 클라이언트가 컨텍스트가 사용할 전략을 정해서 전달하게 구현 → DI 구조라고 이해 가능
- 하지만 아직 큰 장점은 보이지 않음
3.3 JDBC 전략 패턴의 최적화
3.3.1 전략 클래스의 추가 정보
- 추가 정보가 있을 때 바뀌는 로직의 클래스에 정보 객체를 추가시킴으로써 구현이 가능하다
3.3.2 전략과 클라이언트의 동거
- 2가지 문제점
- DAO 메소드마다 새로운 StatementStrategy 구현 클래스를 만들어야 한다는 점.
- DAO 메소드에서 StatementStrategy에 전달할 User와 같은 부가적인 정보가 있는 경우, 이를 위해 오브젝트를 전달받는 생성자와 이를 저장해둘 인스턴스 변수를 번거롭게 만들어야 한다는 점.
로컬 클래스
- 독립된 파일 → UserDao 내부 클래스로 변환
- 자신이 선언된 곳의 정보에 접근 가능. ex) User 추가 정보
익명 내부 클래스
3.4 컨텍스트와 DI
3.4.1 JdbcContext의 분리
- jdbcContextWithStatementStrategy()를 UserDao 클래스 밖으로 독립시켜서 모든 DAO가 사용할 수 있게 해보자
클래스 분리
빈 의존관계 변경
3.4.2 JdbcContext의 특별한 DI
스프링 빈으로 DI
코드를 이용하는 수동 DI
3.5 템플릿과 콜백
- 전략 패턴의 기본 구조에 익명 내부 클래스를 활용하는 방식을 스프링에서는 템플릿/콜백 패턴이라고 부릅니다.
- 템플릿
- 고정된 작업 흐름을 가진 코드를 재사용하는 것
- 콜백
- 템플릿 안에서 호출되는 것을 목적으로 만들어진 오브젝트
Leave a comment