Intro
- 개방 폐쇄 원칙(OCP) : 변화의 특성이 다른 부분을 구분해주고, 각각 다른 목적과 다른 이유에 의해 다른 시점에 독립적으로 변경될 수 있는 효율적인 구조를 만들어주는 것
- 템플릿 : 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록하는 방법
3.1 다시 보는 초난감 DAO
3.1.1 예외처리 기능을 갖춘 DAO
- DB 커넥션이라는 제한적인 리소스를 공유해 사용하는 서버에서 동작하는 JDBC 코드에는 반드시 지켜야 할 원칙이 있음. → 예외처리 (예외가 발생할 경우 리소스를 반환하도록 만들어야함.)
- 일반적으로 서버에서는 제한된 개수의 DB 커넥션을 만들어서 재사용 가능한 풀로 관리함.
- getConnection()으로 가져간 커넥션을 close()해서 돌려줘야지만 다시 풀에 넣었다가 다음 커넥션 요청이 있을 때 재사용할 수 있음.
- Connection, PreparedStatement
- close() 메소드 - 만들어진 걸 종료하는 것이라고 볼 수 있지만 보통 리소스를 반환한다는 의미로 이해하는 것이 좋음.
- 풀 방식으로 운영됨. (미리 정해진 풀 안에 제한된 수의 리소스를 만들어두고 필요할 때 할당, 반환하면 다시 풀에 넣는 방식임.)
- 요청이 많은 서버 환경에서는 풀 방식 선호.
- JDBC 코드에서는 리소스를 반환하도록 try/catch/finally 구문 사용을 권장함.
3.2 변하는 것과 변하지 않는 것
3.2.1 JDBC try/catch/finally 코드의 문제점
- 코드가 복잡하고 중첩되어 있음. → 변하지 않는, 그러나 많은 곳에서 중복되는 코드와 로직에 따라 자꾸 확장되고 자주 변하는 코드를 분리
3.2.2 분리와 재사용을 위한 디자인 패턴 적용
템플릿 메소드 패턴의 적용
- 상속을 통해 기능 확장해서 사용.
- 변하지 않는 부분은 슈퍼클래스에 두고 변하는 부분은 추상 메소드로 정의해둬서 서브클래스에서 오버라이드하여 새롭게 정의해 쓰도록 하는 것임.
- 문제점
- DAO 로직마다 상속 통해 새로운 클래스 만들어야함.
- 확장구조가 클래스를 설계하는 시점에서 고정되어 버림.
전략 패턴의 적용
- 개방폐쇄원칙(OCP)을 잘 지키는 구조이면서도 템플릿 메소드 패턴보다 유연하고 확장성이 뛰어난 것이, 오브젝트를 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 전략 패턴임.
DI 적용을 위한 클라이언트/컨텍스트 분리
- 전략 패턴에 따르면 Context가 어떤 전략을 사용하게 할 것인가는 Context를 사용하는 앞단의 Client가 결정하는게 일반적임.
- DI란 전략 패턴의 장점을 일반적으로 활용할 수 있도록 만든 구조임.
- 마이크로 DI
- DI의 가장 중요한 개념은 제3자의 도움을 통해 두 오브젝트 사이의 유연한 관계가 설정되도록 만든다는 것임.
- DI의 장점을 단순화해서 IoC 컨테이너의 도움 없이 코드 내에서 적용한 경우를 마이크로 DI라고 함.
3.3 JDBC 전략 패턴의 최적화
문제 상황
- DAO 메소드마다 새로운 StatementStrategy 구현 클래스를 만들어야 함. → 클래스 파일의 수 증가
- DAO 메소드에서 StatementStrategy에 전달할 User와 같은 부가적인 정보가 있는 경우, 이를 위해 오브젝트를 전달받는 생성자와 이를 저장해둘 인스턴스 변수를 번거롭게 만들어야 함.
3.3.2 전략과 클라이언트의 동거
로컬 클래스
- 해결법 - StatementStrategy 전략 클래스를 매번 독립된 파일로 만들지 말고 UserDao 클래스 안에 내부 클래스로 정의하기.
- 중첩 클래스(nested class)의 종류
- 스태틱 클래스 - 독립적으로 오브젝트로 만들어질 수 있음.
- 내부 클래스 - 자신이 정의된 클래스의 오브젝트 안에서만 만들어질 수 있음.
- 범위에 따라 구분
- 멤버 내부 클래스 - 멤버 필드처럼 오브젝트 레벨에 정의
- 로컬 클래스 - 메소드 레벨에 정의
- 익명 내부 클래스 - 이름을 갖지 않는 클래스. 클래스를 재사용할 필요가 없고, 구현한 인터페이스 타입으로만 사용할 경우에 유용함.
- 범위에 따라 구분
3.4 컨텍스트와 DI
3.4.2 JDBCContext의 특별한 DI
스프링 빈으로 DI
- 인터페이스를 사용하지 않고 DI를 적용하는 것은 문제가 있지 않을까? 스프링 DI의 기본 의도에 맞게 JdbcContext의 메소드를 인터페이스를 뽑아내어 정의해두고, 이를 UserDao에서 사용해야 하지 않을까?
- 꼭 인터페이스 사용안해도 됨.
- 스프링의 DI는 객체의 생성과 관계설정에 대한 제어권한을 오브젝트에서 제거하고 외부로 위임했다는 IoC 개념을 포괄함. → JdbcContext를 스프링을 이용해 UserDao 객체에서 사용하게 주입했다는 건 DI 따른 것임.
- JdbcContext를 UserDao와 DI 구조로 만들어야 할 이유
- JdbcContext가 스프링 컨테이너의 싱글톤 레지스트리에서 관리되는 싱글톤빈이 되기 때문임.
- JdbcContext가 DI를 통해 다른 빈에 의존하고 있음.
- DI를 위해서는 주입되는 오브젝트와 주입받는 오브젝트 양쪽 모두 스프링 빈으로 등록돼야 함.
- 클래스를 바로 사용하는 코드 구성을 DI에 적용하는 것은 가장 마지막 단계에서 고려해볼 사항임.
- 장점 - 오브젝트 사이의 실제 의존관계가 설정파일에 명확하게 드러남.
- 단점 - DI의 근본적인 원칙에 부합하지 않은 구체적인 클래스와의 관계가 설정에 직접 노출됨.
코드를 이용하는 수동 DI
- 장점 - JdbcContext가 UserDao의 내부에서 만들어지고 사용되면서 관계를 외부에는 드러내지 않아도 됨.
- 단점 - JdbcContext를 여러 오브젝트가 사용하더라도 싱글톤으로 만들 수 없고, DI 작업을 위한 부가적인 코드가 필요함.
3.5 템플릿과 콜백
- 템플릿/콜백 패턴
- 템플릿 - 전략 패턴의 컨텍스트
- 템플릿 메소드 패턴은 고정된 틀의 로직을 가진 템플릿 메소드를 슈퍼클래스에 두고, 바뀌는 부분을 서브클래스의 메소드에 두는 구조로 이루어짐.
- 콜백 - 익명 내부 클래스로 만들어지는 오브젝트
- 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트를 말함.
- 파라미터로 전달되지만 값을 참조하기 위한 것이 아니라 특정 로직을 담은 메소드를 실행시키기 위해 사용함.
- 템플릿 - 전략 패턴의 컨텍스트
3.5.1 템플릿/콜백의 동작원리
템플릿/콜백의 특징
- 템플릿/콜백의 작업 흐름
- [1] 클라이언트는 템플릿 안에서 실행될 로직을 담은 콜백 오브젝트 생성. (콜백은 클라이언트가 템플릿의 메소드를 호출할 때 파라미터로 전달함.)
- [2] 클라이언트는 콜백 전달 및 template 호출
- [3] 템플릿 workflow 시작
- [4] 템플릿은 참조 정보 생성
- [5] 템플릿은 콜백 오브젝트의 메소드를 호출함.
- [6] 콜백은 client final 변수를 참조
- [7] 콜백은 참조정보를 이용해 작업 수행
- [8] 콜백은 콜백 작업 결과를 템플릿에 돌려줌.
- [9] 템플릿은 콜백이 돌려준 정보를 사용해 작업 마저 수행
- [10] 템플릿은 경우에 따라 최종결과를 클라이언트에 돌려줌.
- 템플릿/콜백 방식의 특징
- 콜백은 단일 메소드 인터페이스를 사용함.
- 콜백 인터페이스의 메소드에는 보통 파라미터가 있음. 파라미터는 템플릿의 작업 흐름 중에 만들어지는 컨텍스트 정보를 전달받을 때 사용됨
- 템플릿/콜백 방식에서는 매번 메소드 단위로 사용할 오브젝트를 새롭게 전달받음.
- 일반 DI라면 템플릿에 인스턴스 변수를 만들어두고 사용할 의존 오브젝트를 수정자 메소드로 받아서 사용함.
- 콜백 오브젝트가 내부 클래스로서 자신을 생성한 클라이언트 메소드 내의 정보를 직접 참조.
- 클라이언트와 콜백이 강하게 결합됨.
- 템플릿/콜백 방식은 전략 패턴과 DI의 장점을 익명 내부 클래스 사용 전략과 결합한 독특한 활용법임.
3.5.3 템플릿/콜백의 응용
- 스프링의 기본이 되는 전략 패턴과 DI는 물론이고 템플릿/콜백 패턴도 익숙해지도록 학습할 필요가 있다.
- 고정된 작업 흐름을 갖고 있으면서 반복되는 코드가 있다면, 중복되는 코드를 분리할 방법을 생각해보는 습관을 기르자.
- 가장 전형적인 템플릿/콜백 패턴의 후보는 try/catch/finally 블록을 사용하는 코드임.
중복의 제거와 템플릿/콜백 설계
- 템플릿에 담을 작업 흐름은 어떤 것인지 살펴보기.
- 템플릿이 콜백에게 전달해줄 내부의 정보는 무엇인가? 콜백이 템플릿에게 돌려줄 내용은 무엇인가?
- 템플릿과 콜백의 경계를 정하고 템플릿이 콜백에게, 콜백이 템플릿에게 각각 전달하는 내용이 무엇인지 파악하는 게 가장 중요함. 이에 따라 콜백의 인터페이스를 정의해야하기 때문임.
제네릭스를 이용한 콜백 인터페이스
- 파일을 라인 단위로 처리해서 만드는 결과의 타입을 다양하게 가져가고 싶다면, 자바 언어에 타입 파라미터라는 개념을 도입한 제네릭스를 이용하면 됨.
3.6 스프링의 JdbcTemplate
public class UserDao {
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private JdbcTemplate jdbcTemplate;
private RowMapper<User> userMapper =
new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
return user;
}
};
public void add(final User user) {
this.jdbcTemplate.update("insert into users(id, name, password)values(?,?,?)",
user.getId(), user.getName(), user.getPassword());
}
public User get(String id) {
return this.jdbcTemplate.queryForObject("select * from users where id = ?",
new Object[] {id}, this.userMapper);
}
public void deleteAll() {
this.jdbcTemplate.update("delete from users");
}
public int getCount() {
return this.jdbcTemplate.queryForInt("select count(*) from users");
}
public List<User> getAll() {
return this.jdbcTemplate.query("select * from users order by id",
this.userMapper);
}
}