저번에는 UserDao에 try-catch-finally를 추가했다.
이제 이걸 적용하려면 이 예외처리를 기존의 코드에 하나하나 추가해줘야 한다.
당연히 말도 안된다.
코드를 빼먹을 수도 있고, 수정할 때마다 모두 찾아서 수정을 해줘야한다.
그렇기에 반드시 개선해야 하는 부분이다.
public void deleteAll() throws SQLException {
Connection connection = null;
PreparedStatement preparedStatement;
try{
connection = dataSource.getConnection();
preparedStatement = connection.prepareStatement("delete from users");
preparedStatement.executeUpdate();
preparedStatement.close();
} finally{
if(connection != null){
connection.close();
}
}
}
코드를 보면 Connection, PreparedStatement를 준비하는 부분과 finally 부분은 모두 공통으로 변하지 않는 것을 볼 수 있다.
중간 부분만 바꾸면 되는것이다.
메서드로 추출하는 방법도 있지만 큰 이점은 없어보이기에, 템플릿 메서드 패턴을 적용해보려고 한다.
템플릿 메서드 패턴은 상속을 통해 기능을 확장해서 사용하는 방법이다.
변하지 않는 부분은 슈퍼클래스에 두고, 변하는 부분은 추상 메서드로 정의해서 서브클레스에서 오버라이드해서 새롭게 정의해 쓰도록 만드는 것이다.
public class UserDaoDeleteAll extends UserDao {
public UserDaoDeleteAll(DataSource dataSource) {
super(dataSource);
}
@Override
protected PreparedStatement makeStatement(Connection c) throws SQLException {
return c.prepareStatement("delete from users");
}
}
하지만 여기서 템플릿 메서드의 단점은 사용하고 싶은 sql문마다 dao 클래스를 만들어줘야 한다는 것이다.
이거보다 더 확장적인 방법은 오브젝트를 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 전략 패턴이다.
좌측의 Context의 contextMethod()에서 일정한 구조를 가지고 동작하다가 특정 확장 기능은 Strategy 인터페이스를 통해 외부의 독립된 전략 클래스에 위임하는 것이다.
deleteAll()에서 변하지 않는 부분이라고 한 것이 바로 contextMethod()가 되는 것이다.
변하는 부분을 StatementStrategy로 하고 인터페이스를 만든다.
public interface StatementStrategy {
PreparedStatement makePreparedStatement(Connection c) throws SQLException;
}
그리고 이거를 상속받아, 변하는 부분을 작성한다.
여기에서는 deleteAll이다.
public class DeleteAllStatement implements StatementStrategy {
@Override
public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
return c.prepareStatement("DELETE FROM users");
}
}
그리고 삭제할 때마다 해당 클래스를 생성해서 함수를 호출하는 것이다.
public void deleteAll() throws SQLException {
Connection connection = null;
PreparedStatement preparedStatement;
try{
connection = dataSource.getConnection();
StatementStrategy statementStrategy = new DeleteAllStatement();
preparedStatement = statementStrategy.makePreparedStatement(connection);
preparedStatement.executeUpdate();
preparedStatement.close();
} finally{
if(connection != null){
connection.close();
}
}
}
이렇게하면 뭐..가능은 하지만 항상 클래스를 생성해줘야 하고, 구체적인 클래스인 DeleteAllStatement에 의존하기 때문에 OCP에 잘 맞는다고 할 수는 없다.
더 발전시키기 위해 전략패턴을 더 생각해보자.
Context가 어떤 전략을 사용할지는 Context를 사용하는 Client가 결정하도록 한다.
Client가 구체적인 전략을 선택해서 Context에 전달하는 것이다.
public void jdbcContextWithStatementStrategy(StatementStrategy statementStrategy) throws SQLException {
Connection connection = null;
PreparedStatement preparedStatement;
try{
connection = dataSource.getConnection();
preparedStatement = statementStrategy.makePreparedStatement(connection);
preparedStatement.executeUpdate();
preparedStatement.close();
} finally{
if(connection != null){
connection.close();
}
}
}
이렇게 공통적으로 동작하는 부분을 분리하고
public void deleteAll() throws SQLException {
StatementStrategy strategy = new DeleteAllStatement();
jdbcContextWithStatementStrategy(strategy);
}
deleteAll()을 클라이언트로 전략 오브젝트를 만들어서 컨텍스트를 호출하도록 한다.
사용할 전략 클래스는 DeleteAllStatement이고 컨텍스트로 분리한 jdbcContextWithStatementStrategy() 메서드를 호출한다.
'Spring > 토비의 스프링' 카테고리의 다른 글
[토비의 스프링] 3.4 컨텍스트와 DI (0) | 2025.05.29 |
---|---|
[토비의 스프링] 3.3 JDBC 전략 패턴의 최적화 (0) | 2025.05.26 |
[토비의 스프링] 3.1 다시 보는 초난감 DAO (0) | 2025.05.21 |
[토비의 스프링] 2.5 학습 테스트로 배우는 스프링 (0) | 2025.05.20 |
[토비의 스프링] 2.4 스프링 테스트 적용 (1) | 2025.05.20 |