본문 바로가기

개발 기록/스프링

[토비의 스프링 정리] #3 템플릿의 대해서 리뷰 및 기록

토비의 스프링을 읽고 개인적으로 기록을 하고 있어요 :)


템플릿: 변화가 일어나지 않는 부분과, 변화가 일어나는 부분을 독립시켜서 효과적으로 활용할 수 있도록 하는 방법

 

예외 처리 없는 deleteAll() 함수 로직의 대해서 예외가 발생했을 때의 로직을 추가해 보자.

// 수정 전
public void deleteAll() throws SQLException {
    Connection c = dataSource.getConnection();
    PreparedStatement ps = c.prepareStatement("delete from users");
    ps.executeUpdate();
    ps.close();
    c.close();
}

// 수정 후
public void deleteAll() throws SQLException {
    Connection c = null;
    PreparedStatement ps = null;

    try {
        c = dataSource.getConnection();
        ps = c.prepareStatement("delete from users");
        ps.executeUpdate();
    } catch (SQLException e) {
        throw e;
    } finally {
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e){}
        }
        if (c != null) {
            try {
                c.close();
            } catch (SQLException e){}
        }
    }
}

1개의 메서드에서는 이렇게 예외처리를 할 수 있다. 이런 처리가 수십 수백 개가 되었을 때 상상을 해보아라. 수백 개의 메서드를 빠짐없이 정상적으로 close() 할 수 있겠는가?  커넥션풀이 다 차는 문제가 발생한다면 하루종일 수백 개의 메서드를 체크하며 close() 처리가 안되었는지 확인해야 된다.

개선을 해보자 :)

분석!!! -> 변하는 부분과, 변하지 않는 부분을 구분한다. (아주 중요!!)

먼저 해볼 수 있는 건 메서드 추출법. 변하는 부분을 메서드로 추출하고 변하지 않는 부분에서 호출 하도록 수정

이 방법은 거꾸로 됐다. deleteAll(), getCount() 함수에서 변하지 않는 예외처리는 놔두고, 변하는 비즈니스 로직만을 분리해서 별도로 호출하게 된다. 거꾸로 된 모양새다.

그래서 시도해 볼 방법은 템플릿 메서드 패턴이다.

템플릿 메서드 패턴: 변하지 않는 부분은 슈퍼클래스에 두고, 변하는 부분은 추상 메서드로 정의하여 서브클래스에서 정의해서 사용 하는 패턴.

템플릿 메서드 패턴만을 사용하면, 필요 기능의 따라서 모든 서브클래스를 만들어야 하며, 서브클래스를 공통적으로 사용할 수 있는 장치(e.x, 인터페이스)가 없음

그래서 전략패턴을 사용한다.

전략 패턴: 오브젝트를 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 정의
-> prepareStatement 부분이 전략이다. prepareStatement에 기능마다 다른 sql을 작성해주고 변하는 부분. 인터페이스를 상속받아서 기능들을 개발. 자주 변하는 부분만을 분리하여 깔끔하게 분리 [0]

 

템플릿/콜백 패턴: 전략 패턴의 컨텍스트를 템플릿이라 부르고, 익명 내부 클래스로 만들어지는 오브젝트를 콜백이라부른다.

템플릿 메서드에서는 기능들을 별도의 클래스로 만들었지만, 템플릿/콜백에서는 익명 내부 클래스 사용을 선호.

익명 내부 클래스를 선언해서 기능을 만들다 보니, 람다 사용을 하면 엄청나게 깔끔해 보인다. [1]

 

템플릿/콜백 패턴 연습을 해보자. 계산기  프로그램에 최초 덧셈 기능만을 추가하였다.

public class Calculator {
    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) {
            System.out.println(e.getMessage());
            throw e;
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }
        }
    }
}

try/catch문이 무시무시하다[2] 이후에 곱셈 기능을 추가한다면 반복되는 try/catch문을 또 반복하며 곰셈 함수를 작성할거야? 이제는 그러면 안된다. 템플릿/콜백 패턴을 공부했으니 패턴을 적용해보자 :)

공통된 부분을 템플릿으로 만들고, 기능은 callback으로 만든다. 이후에 공통된 부분이 더 보인다면 공통된 부분을 템플릿 내에 추가를 시켜주자. 한 번에 공통된 부분이 안 보일 수도 있어. 괜찮다. 깔끔한 코드는 한번에 만들어지는게 아니니까. 리팩토링을 진행하면 되겠다.

예시를 보자면 파일을 읽고 덧셈, 곱셈 프로그램을 만들었다. 파일을 읽는 부분이 공통된 부분이라 파일을 읽는 부분만 템플릿을 만들었다. 기능이 잘 돌아가는데 코드를 가만 보니, 라인을 읽는 부분도 똑같다. 그렇다면 기존의 템플릿에 파일 읽기 및 라인을 읽는 부분까지 템플릿에 포함시키면 되겠다.

 

스프링에서의 DB 작업은 정말 간단하다. 원래 대로라면 DB 작업을 하기 위한 코드를 커넥션 부터 예외처리까지 작성을 해줘야 되지만, 템플릿/콜백 패턴을 활용한 JdbcTemplate가 이미 만들어져 있다. 그런 이유로 우리는 JdbcTemplate를 활용하여 필요한 기능만을 구현해주면 되겠다. [3]

 

[0] 템플릿 메소드 / 전략 패턴 사용

https://github.com/junngo/spring-playground/commit/1feadb87c5b4eb2dffe4bfce4f7e97f6adf81260

[1] 템플릿/콜백 패턴 사용

https://github.com/junngo/spring-playground/commit/2a41456054fc8fc597a0886b8b22e87179d5c52f

[2] 템플릿/콜백 패턴 연습 중(덧셈 기능 추가 -> 템플릿/콜백 패턴 적용)

https://github.com/junngo/spring-playground/commit/1201343e85fa6033677a3cd271774fb5193b5701

https://github.com/junngo/spring-playground/commit/dda53f95e386cbf2b5f16f5f88cdf31123689d00

https://github.com/junngo/spring-playground/commit/dc3c8467dc00d83a084af6dc6bae721eb0c58d52

[3]

https://github.com/junngo/spring-playground/commit/c7d27476a0cdae18898e322776b3aa1934430f8d