Group Study (2023-2024)/Spring 심화

[Spring 심화] 3장 템플릿

y-seo 2023. 11. 21. 20:18

☘️ 3.1 다시 보는 초난감 DAO ☘️

3.1.1 예외처리 기능을 갖춘 DAO

[JDBC 수정 기능의 예외 처리 코드]

  • JDBC 코드는 어떤 상황에서도 가져온 리소스를 반환할 수 있도록 try/catch/finally 구문 사용을 권장한다.

☘️ 3.2 변하는 것과 변하지 않는 것☘️

3.2.1 JDBC try/catch/finally 코드의 문제점

  • 테스트를 통해 DAO마다 예외상황에서 리소스를 반납하는지 체크하게 해야 하지만 ..

3.2.2 분리와 재사용을 위한 디자인 패턴 적용

[메소드 추출]

  • 변하는 부분을 메소드로 빼서 독립시킨다.
  • 이렇게 하면 분리시키고 남은 메소드가 재사용이 필요하고, 분리된 메소드는 DAO 로직마ㅏㄷ 새롭게 만들어서 확장되어야 한다.

[템플릿 메소드 패턴의 적용]

  • 템플릿 메소드 패턴을 이용하여 분리한다.
  • 템플릿 메소드 패턴은 상속을 통해 기능을 확장하여 사용하는 부분이다.
  • 변하지 않는 부분은 슈퍼 클래스에 두고 변하는 부분은 추상 메소드로 정의해둬서 서브 클래스에서 오버라이드하여 새롭게 정의해 쓰도록 하는 것이다.
  • UserDao 클래스의 기능을 확장하고 싶을 때마다 상속을 통해 자유롭게 확장할 수 있고, 확장 때문에 기존의 상위 DAO 클래스에 불필요한 변화는 생기지 않도록 할 수 있다. → OCP 준수
  • DAO 로직마다 상속을 통해 새로운 클래스를 만들어야 한다는 제한이 있다.
  • 확장 구조가 이미 클래스를 설계하는 시점에서 고정되어 버린다.

[전략 패턴의 적용]

  • 오브젝트를 아예 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 전략 패턴이 있다.
  • 이렇게 하면 OCP를 준수하면서도 템플릿 메소드 패턴보다 유연하고 확장성이 뛰어나다.
  • 전략 패턴은 OCP 관점에서 보면 확장에 해당하는 변하는 부분을 별도의 클래스로 만들어 추상화된 인터페이스를 통해 위임하는 방식이다.
  • 하지만 전략 패턴은 필요에 따라 컨텍스트는 유지되면서 전략을 바꾸어 슬 수 있다.

[DI 적용을 위한 클라이언트/컨텍스트 분리]

  • context가 어떤 전략을 사용하게 할 것인가는 client가 결정하는게 일반적이다.
  • client가 구체적인 전략의 하나를 선택하고 오브젝트로 만들어 context에 전달한다.
  • context는 전달받은 strategy 구현 클래스의 오브젝트를 사용한다.
  • 전략 패턴의 장점을 일반적으로 활용할 수 있도록 만든 구조를 DI라고 한다.

[마이크로 DI]

  • DI는 제3자의 도움을 통해 두 오브젝트 사이의 유연한 관계가 설정되도록 만든다.
  • DI를 이루는 오브젝트와 구성요소의 구조나 관계는 다양하게 만들 수 있다.
  • 의존 관계에 있는 두 오브젝트, DI 컨테이너, 클라이언트 총 4개의 오브젝트 사이에서 일어난다.
  • DI가 매우 작은 단위의 코드와 메소드 사이에서 일어나기도 한다.

☘️ 3.3 JDBC 전략 패턴의 최적화 ☘️

3.3.1 전략 클래스의 추가 정보

  • deleteAll()과 add() 두 군데에서 모두 PreparedStatement를 실행하는 JDBC try/catch/finally 컨텍스트를 공유해서 사용할 수 있게 되었다.
  • 앞으로 비슷한 기능의 DAO 메소드가 필요할 때마다 이 statemnet 전략과 jdbcContextWithStratementStategy() 컨텍스트를 활용할 수 있다.

3.3.2 전략과 클라이언트의 동거

  • DAO 메소드마다 새로운 StatementStrategy 구현 클래스를 만들어야 한다는 단점이 있다.

[중첩 클래스의 종류]

  • 다른 클래스 내부에 정의되는 클래스를 이야기 한다.
  • static class : 독립적으로 오브젝트로 만들어 질 수 있는 클래스
  • inner class : 자신이 정의된 클래스의 오브젝트 안에서만 만들어질 수 있는 클래스
    • member inner class : 멤버 필드처럼 오브젝트 레벨에 정의되는 클래스
    • local class : 메소드 레벨에 정의되는 클래스
    • anonymous inner class : 이름을 갖지 않는 클래스, 선언된 위치에 따라 범위가 다르다.

[로컬 클래스]

  • 클래스 파일이 많아지는 문제는 UserDao 클래스 안에 내부 클래스로 정의해버려 해결할 수 있다.
  • 로컬 클래스가 내부 클래스이기 때문에 자신이 선언된 것의 정보에 접근할 수 있다. 번거롭게 생성자를 통해 오브젝트를 전달해줄 필요가 없다.
  • 내부 클래스에서 외부의 변수를 사용할 때는 외부 변수를 반드시 final로 선언해주어야 한다.
  • 메소드마다 추가해야 했던 클래스 파일을 하나 줄일 수 있는 방법이다.

[익명 내부 클래스]

  • 클래스 이름을 제거하여 더 간결하게 만들 수 있다.
  • 클래스 선언과 오브젝트 생성이 결합된 형태로 만들어진다.
  • 상속할 클래스나 구현할 인터페이스 생성자 대신 사용한다.
  • 클래스를 재사용할 필요가 없고 구현한 인터페이스 타입으로만 사용할 경우에 유용하다.

☘️ 3.4 컨텍스트와 DI ☘️

3.4.1 JdbcContext의 분리

  • jdbcContextWithStatementStrategy()를 UserDao 클래스 밖으로 독립시켜서 모든 DAO가 사용할 수 있게 한다.

[빈 의존관계 변경]

  • 스프링의 DI는 기본적으로 인터페이스를 사이에 두고 의존 클래스를 바꾸어 사용하도록 하는게 목적이다.
  • 스프링의 빈 설정은 런타임 시에 만들어지는 오브젝트 레벨의 의존관계에 따라 정의된다.

3.4.2 JdbcContext의 특별한 DI

[스프링 빈으로 DI]

  • 스프링의 DI는 넓게 보면 객체의 생성과 관계 설정에 대한 제어 권한을 오브젝트에서 제거하고 외부로 위임했다는 IoC 개념을 포괄한다.
  • JdbcContext를 UserDao와 DI 구조로 만들어야 하는 이유
    • JdbcContext가 스프링 컨테이너의 싱글톤 레지스트리에서 관리되는 싱글톤 빈이 되기 때문이다.
    • JdbcContext가 DI를 통해 다른 빈에 의존하고 있기 때문이다.

[코드를 이용하는 수동 DI]

  • UserDao 내부에서 직접 DI를 적용하는 방법
  • JdbcContext를 싱글톤으로 만드는 것은 포기해야 한다. DAO바다 하나의 JdbcContext 오브젝트를 갖고 있게 해야 한다.
  • JdbcContext에 대한 제어권을 갖고 생성과 관리를 담당하는 UserDao에게 DI까지 맡긴다.
  • 한 오브젝트의 수정자 메소드에서 다른 오브젝트를 초기화하고 코드를 이용해 DI를 하는 것이다.
  • 인터페이스를 사용하지 않고 DAO와 밀접한 관계를 갖는 클래스를 DI에 적용하는 방법
    • 빈으로 등록하는 방법 → 오브젝트 간 실제 의존관계가 설정파일에 명확하게 드러난다, 구체적인 클래스와의 관계가 설정에 직접 노출된다.
    • 수동으로 DI하는 방법 → 관계가 외부에 드러나지 않는다. 싱글톤으로 만들 수 없고 부가적인 코드가 필요하다.

☘️ 3.5 템플릿과 콜백 ☘️

  • 바뀌지 않는 일정한 패턴을 갖는 작업 흐름이 존재하고 그중 일부분만 자주 바꾸어 사용해야 하는 경우에 적합한 구조를 스프링에서는 템플릿/콜백 패턴이라고 부른다.
  • 탬플릿
    • 고정된 틀 안에 바꿀 수 있는 부분을 넣어서 사용하는 경우
  • 콜백
    • 실행되는 것을 목적으로 다른 오브젝트틔 메소드에 전달되는 오브젝트

3.5.1 템플릿/콜백의 동작원리

[템플릿/콜백의 특징]

  • 보통 단일 메소드 인터페이스를 사용한다.
  • 콜백은 일반적으로 하나의 메소들르 가진 인터페이스를 구현한 익명 내부 클래스로 만들어진다.
  • 콜백 인터페이스의 메소드에는 보통 파라미터가 있다. 이 파라미터는 템플릿의 작업 흐름 중에 만들어지는 컨텍스트 정보를 전달 받을 때 사용된다.
  • 매번 메소드 단위로 사용할 오브젝틀르 새롭게 전달 받는다.
  • 콜백 오브젝트가 내부 클래스로서 자신을 생성한 클라이언트 메소드 내의 정볼르 직접 찹조한다.

[JdbcContext에 적용된 템플릿/콜백]

  • 템플릿의 작업 흐름이 더 복잡한 경우에는 한 번 이상 콜백을 호출하거나 여러 개의 콜백을 클라이언트로부터 받아서 사용하기도 한다.

3.5.2 편리한 콜백의 재활용

  • 클라이언트인 DAO의 메소드는 간결해지고 최소한의 데이터 액세스 로직만 갖고 있게 된다.
  • 익명 내부 클래스를 사용하기 때문에 상대적으로 코드를 작성하고 읽기가 불편하다.

[콜백의 분리와 재활용]

  • 분리를 통해 재사용이 가능한 코드를 찾아낼 수 있다면 익명 내부 클래스를 사용한 코드를 간결하게 만들 수 있다.

[콜백과 템플릿의 결합]

  • 재사용 가능한 콜백을 담고 있는 메소드라면 DAO가 공유할 수 있는 템플릿 클래스 안으로 옮길 수 있다.
  • 구체적인 구현과 내부의 전략 패턴, 코드에 의한 DI, 익명 내부 클래스 등의 기술은 최대한 감추고 외부에는 꼭 필요한 기능을 제공하는 단순한 메소드만 노출한다.

3.5.3 템플릿/콜백의 응용

[테스트와 try/catch/finally]

  • 만들어진 모든 리소스는 확실히 정리하고 빠져나오도록 만든다.
  • 모든 예외상황에 대해서는 적절한 처리를 해준다.

[중복의 제거와 템플릿/콜백 설계]

  • 템플릿/콜백을 적용할 때에는 템플릿과 콜백의 경계를 정하고 템플릿이 콜백에게, 콜백이 템플릿에게 각각 전달하는 내용이 무엇인지 파악하는게 가장 중요하다.

[템플릿/콜백의 재설계]

  • 여타 로우레벨의 파일 처리 코드가 템플릿으로 분리되고 순수한 계산 로직만 남도록 한다.

[제네릭스를 이용한 콜백 인터페이스]

  • 제네릭스를 통해 다양한 오브젝트 타입을 지원하는 인터페이스나 메소드를 정의할 수 있다.

☘️ 3.6 스프링의 JdbcTemplate ☘️

[테스트 보완]

  • 네거티브 테스트 = 예외상황에 대한 테스트
  • 네거티브 테스트부터 만드는 것이 좋다.

3.6.5 재사용 가능한 콜백의 분리

[DI를 위한 코드 정리]

  • JDBC API를 사용하는 방식, 예외처리, 리소스의 반납, DB 연결을 어떻게 가져올지에 관한 책임과 관심은 모두 JdbcTemplate에 있다. 변경이 일어난다고 해도 UserDao 코드에는 영향을 주지 않는다. 책임이 다른 코드와 낮은 결합도를 유지하고 있다. 특정 템플릿/콜백 구현에 대한 강한 결합을 가지고 있다.