초난감 DAO
초기에 초난감 DAO 코드에 DI를 적용하여 관심에 따라 코드를 분리하고, 확장과 변경에 용이하게 대응할 수 있는 설게구조로 개선하는 작업을 진행하였다. 확장에는 자유롭게 열려있고 변경에는 닫겨있는 객체지향 설계의 핵심 원칙 개발 폐쇄의 원칙(OCP)를 고려해보자.
템플릿이란 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록 하는 방법이다.
예외 처리 기능을 갖춘 DAO
try/catch/finally 블록을 적용하여 예외상황에 대한 처리를 진행할 수 있다. 서버 환경에서도 안정적으로 수행될 수 있으며 DB연결 기능을 자유롭게 확장할 수 있는 DAO 가 완성되었다. 하지만 아래의 코드를 확인하며 try/catch/finally의 블록이 2중으로 중첩되고 모든 메소드마다 반복되는 것을 볼 수 있다.
...
public void deleteAll() throws SQLException{
Connection c = null;
PreparedStatement ps = null;
try {
c = dataSource.getConnection();
ps = c.prepareStatement(
"delete from user");
ps.executeUpdate();
}catch (SQLException e){
throw e;
}finally{
if(ps != null){
try {
ps.close();
}catch (SQLException e){
}
}
if(ps != null){
try {
c.close();
}catch (SQLException e){
}
}
}
}
분리와 재사용을 위한 디자인 패턴 적용
분리와 재사용을 위해 템플릿 메소드 패턴을 이용하여 분리할 수 있다. 템플릿 메소드 패턴은 상속을 통해 기능을 확장해서 사용하는 것이다. 변하지 않는 부분은 슈퍼클래스에 두고 변하는 부분은 추상메소드를 정의하여 서브 클래스에서 오버라이드 하여 새롭게 정의하여 쓸 수 있다.
해당 패턴을 사용할 경우에는 클래스의 기능 확장시 상속을 통해 자유롭게 확장할 수 있고, 기존의 상위 DAO의 불필요한 변화는 생기지 않도록 할 수 있다. 하지만 각 메소드 생성마다 클래스를 생성해야하고, 확장 구조가 고정되어 버린다는 단점이 있다.
전략 패턴 적용
개방 폐쇄 원칙(OCP)을 잘 지키는 구조이면서도 템플릿 메소드 패턴보다 유연하고 확장성이 뛰어난 것이, 오브젝트를 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 전략패턴이다.
public class UserDaoDeleteAll implements StatementStrategy{
public PreparedStatement makePreparedStatemnt(Connection c) throws SQLException{
PreparedStatement ps = c.prepareStatement("delete from user");
return ps;
}
}
public interface StatementStrategy {
PreparedStatement makePreparedStatemnt(Connection c) throws SQLException;
}
public class UserDao {
...
public void deleteAll() throws SQLException{
Connection c = null;
PreparedStatement ps = null;
try {
c = dataSource.getConnection();
StatementStrategy strategy = new UserDaoDeleteAll();
ps = strategy.makePreparedStatemnt(c);
ps.executeUpdate();
}
...
}
위의 전략패턴은 컨텍스트 안에서 이미 구체적인 전략 클래스인 UserDaoDeleteAll을 사용하도록 고정되어있다. 전략 패턴은 필요에 따라 컨텍스트는 유지하고 전략은 바뀔 수 있어야한다. DI 적용을 자유롭게 하기 위해 클라이언트와 컨텍스트를 분리할 수 있어야 한다.
컨텍스트와 DI
JdbcContext와 작업 메소드들의 분리될 수 있다. JdbcContext를 UserDAO에서 완전히 분리를 하고 DI를 통해 연결될 수있도록 설정을 진행 할 수 있다. 의존관계 주입(DI)에 따르면 인터페이스를 사이에 두어 클래스 레벨에서는 의존관계가 고정되지 않게 하고, 런타임 시에 의존할 오브젝트와의 관계를 다이나믹하게 주입해주는 것이 적합하다. JdbcContext를 UserDAO에서 분리하고 DAO에서 사용할 수 있도로 하는것은 DI의 기본을 따르는 것이다.
이를 통해 JdbcContext가 스프링 컨테이너의 싱글톤 레지스트리에서 관리되는 싱글톤 빈이 되어 여러 오브젝트에서 공유하여 사용 될 수 있다.
템플릿과 콜백
템플릿은 고정된 작업 흐름을 가진 코드를 재사용한다는 의미에서 붙인이름이다. 콜백은 템플릿 안에서 호출되는 것을 목적으로 만들어진 오브젝트를 말한다.
템플릿(template)
템플릿은 어떤 목적을 위해 미리 만들어둔 모양이 있는 틀을 가리킨다. 프로그래밍에서는 고정된 틀 안에 바꿀 수 있는 부분을 넣어서 사용하는 경우에 템플릿이라고 부른다.
JSP는 HTML이라는 고정된 부분에 EL과 스크립릿이라는 변하는 부분을 넣은 일종의 템플릿 파일이다.
콜백(callback)
콜백은 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트를 말한다. 파라미터로 전달되지만 값을 참조하기 위한 것이 아니라 특정 로직을 담은 메소드를 실행시키기 위해 사용한다. 자바에서는 메소드 자체를 파라미터로 전달할 방법이 없기 때문에 메소드가 담긴 오브젝트를 전달해야한다. 그래서 function object 라고도 한다.
템플릿/콜백 특징
템플릿/콜백 패턴의 콜백은 보통 단일 메소드 인터페이스를 사용한다. 템플릿 작업 흐름 중 특정 기능을 위해 한 번 호출되는 경우가 일반적이기 때문이다. DI 방식의 전략 패턴구조와 유사하다.
클라이언트가 템플릿 메소드를 호출하며 콜백 오브젝트를 생성 및 전달하고 작업 결과를 템플릿으로부터 템플릿은 콜백으로 부터 받은 정보를 사용하여 작업을 마주 수행한다. 결과 값을 클라이언트에 다시 돌려준다.
템플릿/콜백 방식은 전략 패턴과 DI의 장점을 익명 내부 클래스 사용전략과 결합한 활용법이라고 할 수 있다.
...
public class CalCultator {
public Integer calcSum(String filePath) throws IOException {
BufferedReader br = null ;
try {
br= new BufferedReader(new FileReader(filePath));
Integer sum = 0;
String line = null;
while((line = br.readLine()) != null ){
sum += Integer.valueOf(line);
}
return sum;
}catch (IOException e){
throw e;
}
finally {
if(br != null){
try {
br.close();
}catch (IOException e){
System.out.println(e.getMessage());
}
}
}
}
}
@Test
public void CalcSumTest() throws IOException {
CalCultator cal = new CalCultator();
int sum = cal.calcSum(getClass()
.getResource("numbers.txt")
.getPath());
assertThat(sum).isEqualTo(10);
}
JdbcContext와 같이 예외처리에 대한 적절한 처리가 필요한 예제가 있다. 파일을 읽어서 처리하는 비슷한 기능이 새로 필요할 때마다 해당 코드를 복사해 적용할 수 없다. 해당 코드에 템플릿/콜백 패턴을 적용하여 효율적으로 리팩토링을 진행해볼 수 있다.
public class CalCultator {
public Integer calcSum(String filePath) throws IOException {
BufferedReaderCallback sumCallback = new BufferedReaderCallback() {
@Override
public Integer doSomethingWithReader(BufferedReader br) throws IOException {
Integer sum = 0;
String line = null;
while ((line = br.readLine()) != null) {
sum += Integer.valueOf(line);
}
return sum;
}
};
return fileReadTemplate(filePath, sumCallback);
}
public Integer fileReadTemplate(String filepath
, BufferedReaderCallback callback)
throws IOException{
BufferedReader br = null;
try {
br= new BufferedReader(new FileReader(filepath));
int ret = callback.doSomethingWithReader(br);
return ret;
}catch (IOException e){
throw e;
}
finally{
try {
br.close();
}catch (IOException e){
System.out.println(e.getMessage());
}
}
}
}
public interface BufferedReaderCallback {
Integer doSomethingWithReader(BufferedReader br) throws IOException;
}
public class CalCultator {
...
public Integer lineReadTemplate(String filepath, LineCallback callback, int intVal)
throws IOException{
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader((filepath)));
Integer res = intVal;
String line = null;
while((line = br.readLine())!= null){
res = callback.doSomethingWithLine(line,res);
}
return res;
}catch (IOException e){
throw e;
}
finally {
try {
br.close();
}catch (IOException e){
System.out.println(e.getMessage());
}
}
}
public Integer calcMultiply(String filePath) throws IOException{
LineCallback multiplyCallback = new LineCallback(){
@Override
public Integer doSomethingWithLine(String line, Integer value)
throws IOException {
return value * Integer.valueOf(line);
}
};
return lineReadTemplate(filePath,multiplyCallback,1);
}
}
public interface LineCallback {
Integer doSomethingWithLine(String line, Integer value) throws IOException;
}
CalCultator cal;
String numFilePath;
@BeforeEach
void setup(){
cal = new CalCultator();
numFilePath = getClass()
.getResource("numbers.txt")
.getPath();
}
@Test
public void calcMultiplyTest() throws IOException {
assertThat(cal.calcMultiply(this.numFilePath)).isEqualTo(24);
}
템플릿/콜백은 스프링이 객체지향 설계와 프로그래밍에 얼마나 가치를 두고 있는지 보여주는 예이다. 스프링이 제공하는 템플릿/콜백을 잘 사용하는 것은 물론이며 직접 템플릿/콜백을 만들어 활용할 수 있어야한다.
JdbcTemplate
스프링에서 제공하는 템플릿/콜백 기술 중 대표적인 것은 JdbcTemplate이 있다. 스프링은 JDBC를 사용하는 DAO에서 사용할 수 있도록 준비된 다양한 템플릿과 콜백을 제공한다.
update()
PreparedStatement 타입의 콜백을 받아서 사용하는 JdbcTemplate의 템플릿 메소드이다.
queryForInt(), queryForObject(), query(),...
SQL 쿼리를 실행하고 ResultSet을 통해 결과 값을 가져오는 코드이다.