• Home
  • About
    • Ara Jo photo

      Ara Jo

      Aspiring Backend Developer :)

    • Learn More
    • Email
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

Book/토비의스프링vol1/ 3장. 템플릿

14 Jun 2021

Reading time ~4 minutes

  • 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해주지 못함
    1. try-catch로 해결
      • 예외가 발생할 수 있는 코드를 모두 try 블록으로 묶어주기
      • 어디서 에러가 터졌는지 모름
      • 코드 중복 + 인덴트
        => 변하는 것과 변하지 않는 것을 분리해보자
    2. 템플릿 메서드 패턴 적용
      • 변하지 않는 부분은 슈퍼클래스에
      • 변하는 부분은 추상메서드로 정의해둬서 서브클래스에서 오버라이드
      • DAO로직마다 상속을 통해 새로운 클래스를 생성해야 함
      • 서브 클래스들이 클래스 레벨에서 컴파일 시점에 이미 관계가 설정되어있음
        => 상속을 통해 확장을 꾀하는 템플릿 메서드 패턴의 단점이 고스란히 드러남
    3. 전략패턴 적용
      • 변하는 부분을 별도의 클래스로 만들어 추상화된 인터페이스를 통해 위임하는 방식
      • DI 적용을 위한 클라이언트/컨텍스트 분리
        • 클라이언트 : DAO 메서드
        • 컨텍스트 : PreparedStatement를 실행하는 JDBC의 작업 흐름
        • 전략 : PreparedStatement를 생성하는 것
      • 템플릿메서드 패턴의 추상클래스를 인터페이스로 바꾼 것
      • 매번 새로운 StatementStrategy 구현 클래스를 만들어야 하므로 클래스 파일이 많아지는 문제가 있음

로컬 클래스 (메서드 레벨에 정의되는 내부 클래스)

  • 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처럼 인스턴스 변수에 선언해두고 사용할 필요가 없음)
  1. 매번 메서드 단위로 사용할 오브젝트를 새롭게 전달받음
  2. 콜백 오브젝트가 내부 클래스로서 자신을 생성한 클라이언트 메서드 내의 정보를 직접 참조
  3. 클라이언트와 콜백이 강하게 결합됨

템플릿/콜백 패턴 VS 전략패턴

  • 전략패턴 : 변경없이 확장하기 편하도록 에 초점
  • 템플릿 콜백 패턴 : 한 어플리케이션 내에서 변하는 부분이 여러 개 나오는 경우
    => try-catch-finally 와 같이 복잡한 코드 내 중복 코드 추출하여 재사용하려는 경우

콜백의 분리, 재사용

  • 복잡한 익명 내부 클래스 사용을 최소화해서 코드가 간결해짐
  • 변하는 부분(단순 sql 실행문)만 파라미터(final로 선언)로 빼고 나머지를 메서드로 추출
    => 재활용 가능한 콜백을 담은 메서드 생성

콜백과 템플릿의 결합

  • 재사용 가능한 콜백을 담고 있는 메서드를 모든 DAO가 공유할 수 있도록 템플릿 클래스로 옮기기
  • 구체적인 구현, 내부의 전략패턴, 코드에 의한 DI, 익명 내부 클래스 => 최대한 감추기
  • 꼭 필요한 기능을 제공하는 단순 메서드 => 외부에 노출
  • 고정된 작업 흐름을 갖고 있으면서 자주 반복되는 코드가 있다면?
    1. 먼저 메서드로 분리 시도
    2. 그 중 일부 작업을 필요에 따라 바꾸어 사용해야 한다면 인터페이스를 사이에 두고 분리 (전략 패턴, DI로 의존관계 관리)
    3. 그런데 바뀌는 부분이 동시에 여러 종류라면 템플릿/콜백 패턴 적용 고려

스프링 JdbcTemplate

  • 스프링에서 제공하는 JDBC 코드용 기본 template
    • JDBC API를 사용하는 방식, 예외처리, 리소스의 반납, DB 연결을 어떻게 가져올지에 대한 책임과 관심은 모두 JdbcTemplate이 가져가고
    • UserDao에는 User 정보를 DB에 넣거나 가져오거나 조작하는 방법에 대한 핵심로직만 담기게 됨
      => 변경이 일어나도 UserDao 코드에는 아무런 영향을 주지 않음 (낮은 결합도)
      +) 더 낮은 결합도를 유지하고 싶다면 JdbcTemplate을 독립적 빈으로 등록하고 인터페이스를 통해 DI 받아 사용하도록 만들 수 있음
  • 더 개선 가능한 부분?
    1. 인스턴스 변수로 설정된 userMapper : 한 번 만들어지면 변경되지 않으므로 아예 UserDao 빈의 DI용 프로퍼티로 만드는 방법
    2. DAO에서 사용하는 SQL 문장 : UserDao 내부 코드가 아니라 외부 리소스에 담고 읽어와 사용하는 방법 => DB 이름, 필드 변경되어도 UserDao 코드는 변경 필요 없음


booktobi_spring Share Tweet +1