- OCP(개방 폐쇄 원칙) : 확장에는 자유롭게 열려 있고 변경에는 굳게 닫혀 있다는 객체지향 설계의 핵심 원칙
- 어떤 부분은 변경을 통해 그 기능이 다양해지고 확장하려는 성질이 있고
- 어떤 부분은 고정되어 있고 변하지 않으려는 성질이 있음
템플릿
- 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜 효과적으로 활용할 수 있도록 하는 방법
- 템플릿 메서드 패턴 : hook method (변경 x) + abstract method (자유롭게 변경)
기존 deleteAll 의 문제점
public void deleteAll() throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("delete from users");
ps.executeUpdate();
ps.close();
c.close();
}
- delete 과정에서 에러가 발생하면 connection을 close해주지 못함
- try-catch로 해결
- 예외가 발생할 수 있는 코드를 모두 try 블록으로 묶어주기
- 어디서 에러가 터졌는지 모름
- 코드 중복 + 인덴트
=> 변하는 것과 변하지 않는 것을 분리해보자
- 템플릿 메서드 패턴 적용
- 변하지 않는 부분은 슈퍼클래스에
- 변하는 부분은 추상메서드로 정의해둬서 서브클래스에서 오버라이드
- DAO로직마다 상속을 통해 새로운 클래스를 생성해야 함
- 서브 클래스들이 클래스 레벨에서 컴파일 시점에 이미 관계가 설정되어있음
=> 상속을 통해 확장을 꾀하는 템플릿 메서드 패턴의 단점이 고스란히 드러남
- 전략패턴 적용
- 변하는 부분을 별도의 클래스로 만들어 추상화된 인터페이스를 통해 위임하는 방식
- DI 적용을 위한 클라이언트/컨텍스트 분리
- 클라이언트 : DAO 메서드
- 컨텍스트 : PreparedStatement를 실행하는 JDBC의 작업 흐름
- 전략 : PreparedStatement를 생성하는 것
- 템플릿메서드 패턴의 추상클래스를 인터페이스로 바꾼 것
- 매번 새로운 StatementStrategy 구현 클래스를 만들어야 하므로 클래스 파일이 많아지는 문제가 있음
- try-catch로 해결
로컬 클래스 (메서드 레벨에 정의되는 내부 클래스)
- StatementStrategy 전략 클래스를 매번 독립된 파일로 만들지 말고 내부 클래스로 정의
- 마치 로컬 변수를 선언하듯 선언
- 선언된 메서드 내에서만 사용가능
- 내부 클래스이기 때문에 자신이 정의된 메서드의 로컬 변수에 직접 접근가능
- 단, 외부의 변수를 사용할 때는 외부 변수를 반드시 final로 선언해줘야 함
익명 내부 클래스
- 이름을 갖지 않는 클래스
- 클래스 선언과 오브젝트 생성이 결합된 형태로 만들어짐
- 클래스를 재사용할 필요가 없고, 구현한 인터페이스 타입으로만 사용할 경우 유용
new 인터페이스이름() { 클래스 본문 };
- 구현하는 인터페이스를 생성자처럼 이용해 오브젝트로 만든다
JDBC 컨텍스트의 분리
- 일반적인 작업 흐름을 담고 있는 컨텍스트를 모든 dao가 사용할 수 있도록 클래스로 분리
=> 인터페이스가 아닌 클래스 형태의 DI : 온전한 형태의 DI라고 보기는 어려움 - 그러나 객체 생성, 관계 설정에 대한 부분을 외부로 위임
=> IoC의 개념은 적용되었다고 볼 수 있다. - JdbcContext를 UserDao와 DI구조로 만들어야 할 이유?
- JdbcContext가 스프링 컨테이너의 싱글톤 레지스트리에서 관리되는 싱글톤 빈이 되기 때문
- JdbcContext가 DI를 통해 다른 빈에 의존하고 있기 때문
=> 이런 경우 굳이 인터페이스를 두지 말고 강한 결합 관계를 허용하면서 스프링의 빈으로 등록해서 UserDao에 DI 되도록 만들어도 좋음
- 스프링의 빈으로 등록해서 사용하는 방법 : 오브젝트 사이 의존관계가 설정파일에 명확하게 드러난다는 장점, but 구체적인 클래스와의 관계가 설정에 직접 노출된다는 단점
- DAO의 코드를 이용해 수동 DI하는 방법 : 관계가 외부에는 드러나지 않는다는 장점, but 여러 오브젝트가 사용하더라도 싱글톤으로 만들 수 없고, DI 작업을 위한 부가적 코드가 필요하다는 단점
템플릿과 콜백
- 템플릿 : 어떤 목적을 위해 미리 만들어둔 모양이 있는 틀, 전략패턴에서 컨텍스트
- 콜백 : 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트(ex. functional 오브젝트), 변하는 부분, 전략패턴에서 익명내부 클래스로 만들어지는 오브젝트
- 템플릿/콜백 패턴 : 변하지 않는 틀(템플릿)에 변하는 부분(콜백)만 그때그때 바꿔 전달하여 사용하는 패턴
동작원리와 특징
- 단일 메소드 인터페이스 사용 ( vs 전략패턴 : 여러 메서드를 가진 인터페이스 사용)
- 보통 단일 메소드 인터페이스를 구현한 익명 내부 클래스 사용
- 파라미터로 콜백을 제공 (이전의 전략패턴 DI처럼 인스턴스 변수에 선언해두고 사용할 필요가 없음)
- 매번 메서드 단위로 사용할 오브젝트를 새롭게 전달받음
- 콜백 오브젝트가 내부 클래스로서 자신을 생성한 클라이언트 메서드 내의 정보를 직접 참조
- 클라이언트와 콜백이 강하게 결합됨
템플릿/콜백 패턴 VS 전략패턴
- 전략패턴 : 변경없이 확장하기 편하도록 에 초점
- 템플릿 콜백 패턴 : 한 어플리케이션 내에서 변하는 부분이 여러 개 나오는 경우
=> try-catch-finally 와 같이 복잡한 코드 내 중복 코드 추출하여 재사용하려는 경우
콜백의 분리, 재사용
- 복잡한 익명 내부 클래스 사용을 최소화해서 코드가 간결해짐
- 변하는 부분(단순 sql 실행문)만 파라미터(final로 선언)로 빼고 나머지를 메서드로 추출
=> 재활용 가능한 콜백을 담은 메서드 생성
콜백과 템플릿의 결합
- 재사용 가능한 콜백을 담고 있는 메서드를 모든 DAO가 공유할 수 있도록 템플릿 클래스로 옮기기
- 구체적인 구현, 내부의 전략패턴, 코드에 의한 DI, 익명 내부 클래스 => 최대한 감추기
- 꼭 필요한 기능을 제공하는 단순 메서드 => 외부에 노출
- 고정된 작업 흐름을 갖고 있으면서 자주 반복되는 코드가 있다면?
- 먼저 메서드로 분리 시도
- 그 중 일부 작업을 필요에 따라 바꾸어 사용해야 한다면 인터페이스를 사이에 두고 분리 (전략 패턴, DI로 의존관계 관리)
- 그런데 바뀌는 부분이 동시에 여러 종류라면 템플릿/콜백 패턴 적용 고려
스프링 JdbcTemplate
- 스프링에서 제공하는 JDBC 코드용 기본 template
- JDBC API를 사용하는 방식, 예외처리, 리소스의 반납, DB 연결을 어떻게 가져올지에 대한 책임과 관심은 모두 JdbcTemplate이 가져가고
- UserDao에는 User 정보를 DB에 넣거나 가져오거나 조작하는 방법에 대한 핵심로직만 담기게 됨
=> 변경이 일어나도 UserDao 코드에는 아무런 영향을 주지 않음 (낮은 결합도)
+) 더 낮은 결합도를 유지하고 싶다면 JdbcTemplate을 독립적 빈으로 등록하고 인터페이스를 통해 DI 받아 사용하도록 만들 수 있음
- 더 개선 가능한 부분?
- 인스턴스 변수로 설정된 userMapper : 한 번 만들어지면 변경되지 않으므로 아예 UserDao 빈의 DI용 프로퍼티로 만드는 방법
- DAO에서 사용하는 SQL 문장 : UserDao 내부 코드가 아니라 외부 리소스에 담고 읽어와 사용하는 방법 => DB 이름, 필드 변경되어도 UserDao 코드는 변경 필요 없음