3.2.1 JDBC trt/catch/finally 코드의 문제점
- 코드가 너무 난잡함
- close()를 제대로 하지 않으면 리소스 문제 발생
- 수정할 코드를 찾는 것이 어려움
- 예외상황을 처리하는 테스트 코드를 만들기 힘듦(인터페이스 구현체를 만들어야 하기 때문)
💡 난잡한 코드를 해결하기 위해 변하는 것과 변하지 않는 것을 분리하여 리팩토링 해야함
3.2.2 분리
- 변하는 것과 변하지 않는 것을 분리
- deleteAll()에서 ps 만이 변하는 부분임
- 변하는 부분만 메소드 추출하면 추출한 메소드를 다른곳에 재사용할 수 있어야 하는데, 이와 반대로 추출된 메소드만 재사용이 필요한 부분으로 변경됨
public void deleteAll() throws SQLException {
PreparedStatement ps = null;
Connection c = null;
try {
c = dataSource.getConnection();
ps = makeStatement(c);
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
...
}
}
private PreparedStatement makeStatement(Connection c) throws SQLException{
PreparedStatement ps = c.prepareStatement("delete from users");
return ps;
}
3.2.3 템플릿 메소드 패턴의 적용
- 템플릿 메소드는 상속을 통해 기능을 확장해서 사용하는 패턴임
- 변하지 않는 부분은 슈퍼 클래스에 두고 변하는 부분은 추상 메소드를 정의해 둬서 서브 클래스에서 오버라이드하여 새롭게 정의하도록 사용하면 됨
- UserDao 클래스를 추상 클래스로 만들고 변하는 부분인 makeStatement()를 추상 메서드로 해서 상속을 받을 클래스를 만들면 됨
- 하지만, 템플릿 메소드 패턴으로 접근하면 DAO 로직마다 상속을 통해 새로운 클래스를 만들어야 하므로 단점이 매우 큼
public abstract class UserDao {
...
public abstract PreparedStatement makeStatement(Connection c) throws SQLException;
}
public class UserDaoDeleteAll extends UserDao{
@Override
public PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement("delete from users");
return ps;
}
}
3.2.4 전략 패턴의 적용
- 전략 패턴은 인터페이스를 사용하여 템플릿 메소드 패턴보다 유연함
- 오브젝트를 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하는 패턴
- 확장 기능은 인터페이스를 통해 외부의 독립된 전략 클래스에 위임
- 전략 패턴의 구조에 따라 PreparedStatement를 만들어줄 인터페이스를 호출하면 됨
- 그림 3-2에서 변하지 않는 부분은 contextMethod() 부분임
- 하지만, 현재는 UserDao가 클래스 레벨에서 이미 구체적으로 어떤 전략을 쓸지(new DeleteAllStatement()) 고정되어 있기 때문에 클라이언트에게 책임을 넘김
public interface StatementStrategy {
PreparedStatement makePrepareStatement(Connection c) throws SQLException;
}
public class DeleteAllStatement implements StatementStrategy{
@Override
public PreparedStatement makePrepareStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement("delete from users");
return ps;
}
}
public void deleteAll() throws SQLException {
PreparedStatement ps = null;
Connection c = null;
try {
c = dataSource.getConnection();
// UserDao가 구체적인 전략을 구현하고 있음(이러면 안됨)
StatementStrategy strategy = new DeleteAllStatement();
ps = strategy.makePrepareStatement(c);
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
...
}
}
3.2.5 DI 적용을 위한 클라이언트/컨텍스트 분리
- 클라이언트가 어떤 전략을 사용할지 결정(new DeleteAllStatement())하고, 해당 결과를 컨텍스트(UserDao)에게 전달
- 결국 이 구조에서 전략 오브젝트 생성과 컨텍스트로의 전달을 담담하는 책임을 분리하는 것이 ObjectFactory이며, 이를 일반화한 것이 DI임
- 결국 DI란, 이러한 전략 패턴의 장점을 일반적으로 활용할 수 있도록 만든 구조임
- 제공받은 전략 오브젝트는 PreparedStatement 생성이 필요한 시점에 호출해서 사용
public void jdbcContextWithStatementStrategy(StatementStrategy stmt) throws SQLException {
PreparedStatement ps = null;
Connection c = null;
try {
c = dataSource.getConnection();
ps = stmt.makePrepareStatement(c);
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
...
}
}
3.2.6 마이크로 DI
- DI의 가장 중요한 개념은 제 3자의 도움을 통해 두 오브젝트 사이의 유연한 관계가 설정되도록 하는 것임
- 일반적인 DI는 두개의 오브젝트, DI 컨테이너, 클라이언트라는 4개의 오브젝트 사이에서 발생됨
- 원시적인 전략 패턴의 경우 클라이언트가 DI 컨테이너의 역할도 함
- 또한, 클라이언트와 전략(의존 오브젝트)가 결합될 수도 있고, 두개의 오브젝트 모두 하나의 클래스 안에 담길 수도 있음
- DI 가 매우 작은 단위의 코드와 메소드 사이에 발생하기도 함
- 얼핏보면 DI 같아 보이진 않지만, 세밀게 관찰해 보면 작은 단위의 DI가 발생하고 있음
- 이렇게 DI의 장점을 단순화해서 IoC 컨테이너의 도움없이 코드 내에서 적용한 경우를 마이크로 DI 라고 함
- 마이크로 DI는 코드에 의한 DI 란 의미로, 수동 DI라고 부를 수 있음
'토비의 스프링 정리' 카테고리의 다른 글
토비의 스프링 - 3.4 컨텍스트와 DI (0) | 2022.09.30 |
---|---|
토비의 스프링 - 3.3 JDBC 전략 패턴의 최적화 (0) | 2022.09.29 |
토비의 스프링 - 3.1 다시 보는 초난감 DAO (0) | 2022.09.29 |
토비의 스프링 - 2.6 2장 정리 (0) | 2022.09.28 |
토비의 스프링 - 2.5 학습 테스트로 배우는 스프링 (0) | 2022.09.28 |