템플릿이란 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록 하는 방법입니다.

OCP - 개방 폐쇄 원칙

클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.

  • 변화의 특성이 다른 부분을 구분해주고, 각각 다른 목적과 다른 이유에 의해 다른 시점에 독립적으로 변경될 수 있는 효율적인 구조를 만들어주는 것.

3.1 다시 보는 초난감 DAO

3.1.1 예외처리 기능을 갖춘 DAO

  • DB 커넥션이라는 제한적인 리소스를 공유해 사용하는 서버에서 동작하는 JDBC 코드에는 반드시 지켜야 할 원칙이 있음 → 예외처리
  • 예외가 발생했을 경우에도 사용한 리소스를 반환하도록 만들어야 함
public void deleteAll() throws SQLException {
		Connection c = dataSource.getConnection();
		
		PreparedStatement ps = c.prepareStatement("delete from users");
		ps.executeUpdate();
		
		ps.close();
		c.close();
}
  • Connection 객체와 PreparedStatement 객체의 close() 메소드가 실행되지 않아서 제대로 리소스가 반환되지 않을 수 있음. (예외 처리가 안되있을시)
  • try/catch로 당장의 문제를 해결할 순 있다! 하지만..

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

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

  • 코드가 복잡함
  • 여러 메소드에 적용할 경우 에러를 잡기가 힘듦

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

public void deleteAll() throws SQLException {
		Connection c = null;
		PreparedStatement ps = null;
		
		try {
		
				c = dataSource.getConnection();
				PreparedStatement 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) {} }
		}
		
		ps.close();
		c.close();
}

메소드 추출

  • 메소드 사용 자체가 재사용이 필요하고 분리된 메소드는 DAO 로직마다 새로 만들어줘야하는 이득없는 상황 발생

템플릿 메소드 패턴 적용

public class UserDaoDeleteAll extends UserDao {
		
		protected PreparedStatement makeStatement(Connection c) throws SQLException {
				PreparedStatement ps = c.prepareStatement("delete from users");
				return ps;
		}
}
  • 변하지 않는 부분은 슈퍼클래스에 두고 변하는 부분은 추상 메소드로 정의해둬서 서브클래스에서 오버라이드하여 새롭게 정의해 쓰도록 하는 것
  • 메소드가 4개일 경우 서브클래스를 4개 만들어야 하는 문제 발생
  • 확장구조가 이미 클래스를 설계하는 시점에서 고정됨

전략 패턴의 적용

  • 변하는 부분을 별도의 클래스로 만들어 추상화된 인터페이스를 통해 위임하는 방식.
// 3-8 StatementStrategy 인터페이스
package springbook.user.dao;
...
public interface StatementStrategy {
		PreparedStatement makePreparedStatement(Connection c) throws SQLException;
}
// 3-9 deleteAll() 메소드의 기능을 구현한 StatementStrategy 전략 클래스
package springbook.user.dao;
...
public class DeleteAllStatement implements StatementStrategy {
		public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
				PreparedStatement ps = c.prepareStatement("delete from users");
				return ps;
		}
}
// 3-10 전략 패턴을 따라 DeleteAllStatement가 적용된 deleteAll() 메소드
public void deleteAll() throws SQLException {
		...
		try {
		
				c = dataSource.getConnection();
				
				StatementStrategy strategy = new DeleteAllStatement();
				ps = strategy.makePreparedStatement(c);
				
				ps.executeUpdate();
				
		} catch (SQLException e) {
				...
}

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

  • 클라이언트가 컨텍스트가 사용할 전략을 정해서 전달하게 구현 → DI 구조라고 이해 가능
  • 하지만 아직 큰 장점은 보이지 않음

3.3 JDBC 전략 패턴의 최적화

3.3.1 전략 클래스의 추가 정보

  • 추가 정보가 있을 때 바뀌는 로직의 클래스에 정보 객체를 추가시킴으로써 구현이 가능하다

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

  • 2가지 문제점
    • DAO 메소드마다 새로운 StatementStrategy 구현 클래스를 만들어야 한다는 점.
    • DAO 메소드에서 StatementStrategy에 전달할 User와 같은 부가적인 정보가 있는 경우, 이를 위해 오브젝트를 전달받는 생성자와 이를 저장해둘 인스턴스 변수를 번거롭게 만들어야 한다는 점.

로컬 클래스

  • 독립된 파일 → UserDao 내부 클래스로 변환
    • 자신이 선언된 곳의 정보에 접근 가능. ex) User 추가 정보

익명 내부 클래스

3.4 컨텍스트와 DI

3.4.1 JdbcContext의 분리

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

클래스 분리

빈 의존관계 변경

3.4.2 JdbcContext의 특별한 DI

스프링 빈으로 DI

코드를 이용하는 수동 DI

3.5 템플릿과 콜백

  • 전략 패턴의 기본 구조에 익명 내부 클래스를 활용하는 방식을 스프링에서는 템플릿/콜백 패턴이라고 부릅니다.
  • 템플릿
    • 고정된 작업 흐름을 가진 코드를 재사용하는 것
  • 콜백
    • 템플릿 안에서 호출되는 것을 목적으로 만들어진 오브젝트

템플릿/콜백의 특징

Categories:

Updated:

Leave a comment