[토비의 스프링] - 템플릿
✔ 템플릿
이 포스팅은 토비의 스프링을 읽고 개인적으로 정리하는 글입니다.
템플릿
템플릿
이란 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록 하는 방법
다시 보는 초난감 DAO
예외처리 기능을 가진 DAO
public void deleteAll() throws SQLException {
Connection c =dataSource.getConnection();
PreparedStatement ps =c.prepareStatement("delete from users");
ps.executeUpdate();
ps.close();
c.close();
}
- 위의 코드는
.close()
를 통해 자원을 반납하기 전에 예외가 발생하면 리소스가 정상적으로 반환되지 않을 수 있다. 이렇게 Pool
에 있던 모든 리소스를 다 사용하고 반납하지 못하게 되면 리소스가 모자라다는 심각한 오류를 내며 서버가 중단될 수 있음.
- 따라서 예외가 발생해도 정상적으로 자원을 반납할 수 있도록 아래와 같은 코드를 작성해야 한다.(책에서는 JDK 1.6 기준이라
try-catch-finally
문법을 사용하지만 현재는 <AutoCloseable> 인터페이스
의 등장으로 인해 try-with-resources
문법을 사용하는 것이 더 효율적이다)
public void deleteAll() throws SQLException {
Connection c =null;
PreparedStatement ps =null;
try {
rc =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) {
}
}
}
}
JDBC 조회 기능의 예외처리
- 위의 Connection, PreparedStatement외에도 ResultSet이 추가된다.
public int getCount() throws SQLException {
Connection c =null;
PreparedStatement ps =null;
ResultSet rs = null;
try {
c =dataSource.getConnection();
ps = c.prepareStatement("select count(*) from users");
rs.next();
return rs.getInt(1);
} catch (SQLException e) {
throw e;
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
}
}
if (ps != null) {
try {
ps .close();
} catch (SQLException e) {
if (c != null) {
try {
c. close();
} catch (SQLException e) {
}
}
}
}
}
}
변하는 것과 변하지 않는 것
JDBC try/catch/finally 코드의 문제점
- 위의 코드처럼 예외처리를 하면 기능상에는 전혀 문제가 없는 코드가 완성되었다. 하지만 코드를 자세히 살펴보면 중복도 많고, 읽기 어려운 코드가 되었다.
- 또한 수정을 원할때도 어느 부분을 수정해야하는지, 수정했을 때 기능이 정상적으로 작동할지 보장할 수 없다.
분리와 재사용을 위한 디자인 패턴 적용
- 변하는 부분을 먼저 메소드 추출로 분리해보자
public void deleteAll() throws SQLException {
...
try {
c =dataSource.getConnection();
ps = makeStatement(c);
ps.executeUpdate();
} catch (SQLException e)
...
}
private PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps;
ps = c.prepareStatement("delete from users");
return ps;
}
- 메소드 분리를 통해 변하는 부분을 추출해보았지만 이를 다른곳에서 재활용할 수 없어보인다.
- 분리하고 남은 부분들이 재사용이 필요하고, 분리한 메소드가 쿼리마다 새로 만들어져야 해서 뭔가 반대로 되었다.
템플릿 메소드 패턴의 적용
- 템플릿 메소드 패턴이란?
- 상속을 통해 기능을 확장해서 사용하는 부분
- 즉, 변하지 않는 부분은 슈퍼클래스에 두고 변하는 부분은 추상 메소드로 정의해둬서 서브클래스에서 오버라이드하여 새롭게 정의해 쓰도록 하는 것
public class UserDaoDeleteAll extends UserDao {
protected PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement("delete from users");
return ps;
}
}
- 템플릿 메소드 패턴의 적용을 통해 어느정도 OCP를 지키면서 구조를 개선했지만 필요한 쿼리마다 계속 클래스를 만들어야 한다는 것은 부담스럽다.
- 또한 확장구조가 이미 클래스를 설계하는 시점에서 고정되어 버린다.
전략 패턴의 적용
- 전략 패턴이란?
- OCP를 잘 지키면서
템플릿 메소드 패턴
보다 유연하고 확장성이 뛰어난 것이, 오브젝트를 아예 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 전략
클라이언트가 전략을 생성해 전략을 실행할 컨텍스트에 주입하는 패턴
- 전략 패턴을 구성하는 3가지 요소
- 전략 메소드를 가진 전략 객체
- 전략 객체를 사용하는 컨텍스트
- 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트
컨텍스트와 DI
JdbcContext의 분리
- 전략 패턴의 구조로 본다면
- UserDao의 메소드는 클라이언트
- 익명 내부 클래스로 만들어지는 것은 개별적인 전략
- jdbcContextWithStatementStrategy() 메소드는 컨텍스트
- 모든 DAO에서 사용할 수 있도록 jdbcContextWithStatementStrategy()를 독립시켜보자!
클래스 분리
- JdbcContext 클래스로 분리해서 DI에 활용하자!
빈 의존관계 변경
UserDao
는 이제 JdbcContext
에 의존하고 있다. 하지만 이는 인터페이스가 아닌 구현체임.
- 보통의 스프링
DI
는 인터페이스를 두고 구현체를 여러개 만들어 바꿔 사용하는 것이 목적이었음
스프링 빈으로 DI
- 이렇게 인터페이스를 사용하지 않고 DI를 적용하는 것은 문제가 있지 않을까?
- 그렇게 해도 상관없지만 꼭 그럴 필요는 없다!
- 의존관계 주입이라는 개념을 충실히 따르면, 인터페이스를 사이에 둬서 클래스레벨에서는 의존관계가 고정되지 않게 하고, 런타임 시에 의존할 오브젝트와의 관계를 동적으로 주입해주는 것이 맞다!
- 하지만 위의 코드에서는 객체의 생성과 관계설정에 대한 제어권한을 오브젝트에서 제거하고 외부로 위임했다는
IoC
의 개념을 포괄하고 있으므로 DI
의 기본을 따르고 있다고 볼 수 있다.
JdbcContext
를 DI
구조로 만들어야 할 이유
JdbcContext
가 스프링 컨테이너의 싱글톤 레지스트리에서 관리되는 싱글톤 빈이 되기 때문!
JdbcContext
가 DI
를 통해 다른 빈에 의존하고 있기 때문!
DI
를 위해서는 양쪽 클래스 모두 빈으로 등록되어야 한다!
- 현재는 인터페이스가 없어서
UserDao
와 JdbcContext
가 매우 강하게 결합되어 있음.
코드를 이용하는 DI
UserDao
내부에서 직접 DI
를 적용하는 방법!
- 이 방법에서는 싱글톤으로 만드는 것을 포기해야 한다. 왜냐하면 DAO마다 하나의
JdbcContext
를 갖고 있게 해야하기 때문이다.
JdbcContext
에 대한 제어권을 스프링이 아닌 사용자가 갖고 생성과 관리를 담당하는 UserDao
에게 DI
까지 맡기는 것!
- 위와 같이 한 오브젝트의 수정자 메소드에서 다른 오브젝트를 초기화하고 코드를 이용해 DI하는 것은 스프링에서도 종종 사용되는 기법
템플릿과 콜백
- 지금까지
UserDao
와 StatementStrategy
, JDbcContext
를 이용해 만든 코드는 일종의 전략 패턴이 적용된 것!
- 복잡하지만 바뀌지 않는 일정한 패턴을 갖는 작업 흐름이 존재하고 그중 일부분만 자주 바꿔서 사용한다면
템플릿/콜백 패턴
이라고 부른다.
콜백은 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트를 말한다.
템플릿 / 콜백의 특징
- 여러 개의 메소드를 가진 일반적인 인터페이스를 사용할 수 있는
전략 패턴
의 전략과 달리 템플릿 / 콜백 패턴
의 콜백은 보통 단일 메소드 인터페이스를 사용한다. 이는 템플릿의 작업 흐름 중 특정 기능을 위해 한 번 호출되는 것이 일반적이기 때문이다.
- 콜백 인터페이스의 메소드에는 보통 파라미터가 있다. 이는 템플릿의 작업 흐름 중에 만들어지는 컨텍스트 정보를 전달받을 때 사용!
템플릿 / 콜백 패턴
에서는 매번 메소드 단위로 사용할 오브젝트를 새롭게 전달받는다는 것이 특징이며 또한 콜백 오브젝트가 내부 클래스로서 자신을 생성한 클라이언트 메소드 내의 정보를 직접 참조한다는 것도 고유한 특징이다.
콜백의 분리와 재활용
- 복잡한 익명 내부 클래스의 사용을 최소화할 수 있는 방법!
- 위의 코드는 바뀌는 sql부분만 파라미터로 받아서 사용한다.
콜백과 템플릿의 결합
- 위에서 만든
executeSql()
메소드를 JdbcContext
내부로 옮겨 여러곳에서 사용할 수 있도록 수정한다.
- 성격이 다른 코드들은 가능한 한 분리하는 편이 낫지만, 이 경우에는 반대다!
- 하나의 목적을 위해 서로 긴밀하게 연관되어 동작하는 응집력이 강한 코드들이기 때문이다!
템플릿 / 콜백의 응용
- 스프링을 사용하는 개발자라면 당연히 스프링이 제공하는
템플릿 / 콜백
기능을 잘 사용할 수 있어야 한다.
- 고정된 작업 흐름을 갖고 있으면서 여기저기서 자주 반복되는 코드가 있다면, 중복되는 코드를 분리할 방법을 생각해보는 습관을 기르자.
- 가장 전형적인
템플릿 / 콜백 패턴
의 후보는 try/catch/finally
블록을 사용하는 코드다.
템플릿/콜백은 스프링이 객체지향 설계와 프로그래밍에 얼마나 가치를 두고 있는지를 잘 보여주는 예다! 우리는 이를 잘 사용하는 것은 물론이고 필요하면 직접 만들어서 활용할 수도 있어야 한다!
Author And Source
이 문제에 관하여([토비의 스프링] - 템플릿), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@ak2j38/토비의-스프링-템플릿
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
이 포스팅은 토비의 스프링을 읽고 개인적으로 정리하는 글입니다.
템플릿
이란 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록 하는 방법public void deleteAll() throws SQLException {
Connection c =dataSource.getConnection();
PreparedStatement ps =c.prepareStatement("delete from users");
ps.executeUpdate();
ps.close();
c.close();
}
.close()
를 통해 자원을 반납하기 전에 예외가 발생하면 리소스가 정상적으로 반환되지 않을 수 있다. 이렇게 Pool
에 있던 모든 리소스를 다 사용하고 반납하지 못하게 되면 리소스가 모자라다는 심각한 오류를 내며 서버가 중단될 수 있음.try-catch-finally
문법을 사용하지만 현재는 <AutoCloseable> 인터페이스
의 등장으로 인해 try-with-resources
문법을 사용하는 것이 더 효율적이다)public void deleteAll() throws SQLException {
Connection c =null;
PreparedStatement ps =null;
try {
rc =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) {
}
}
}
}
public int getCount() throws SQLException {
Connection c =null;
PreparedStatement ps =null;
ResultSet rs = null;
try {
c =dataSource.getConnection();
ps = c.prepareStatement("select count(*) from users");
rs.next();
return rs.getInt(1);
} catch (SQLException e) {
throw e;
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
}
}
if (ps != null) {
try {
ps .close();
} catch (SQLException e) {
if (c != null) {
try {
c. close();
} catch (SQLException e) {
}
}
}
}
}
}
public void deleteAll() throws SQLException {
...
try {
c =dataSource.getConnection();
ps = makeStatement(c);
ps.executeUpdate();
} catch (SQLException e)
...
}
private PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps;
ps = c.prepareStatement("delete from users");
return ps;
}
- 상속을 통해 기능을 확장해서 사용하는 부분
- 즉, 변하지 않는 부분은 슈퍼클래스에 두고 변하는 부분은 추상 메소드로 정의해둬서 서브클래스에서 오버라이드하여 새롭게 정의해 쓰도록 하는 것
public class UserDaoDeleteAll extends UserDao {
protected PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement("delete from users");
return ps;
}
}
- OCP를 잘 지키면서
템플릿 메소드 패턴
보다 유연하고 확장성이 뛰어난 것이, 오브젝트를 아예 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 전략 클라이언트가 전략을 생성해 전략을 실행할 컨텍스트에 주입하는 패턴
- 전략 패턴을 구성하는 3가지 요소
- 전략 메소드를 가진 전략 객체
- 전략 객체를 사용하는 컨텍스트
- 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트
- UserDao의 메소드는 클라이언트
- 익명 내부 클래스로 만들어지는 것은 개별적인 전략
- jdbcContextWithStatementStrategy() 메소드는 컨텍스트
UserDao
는 이제 JdbcContext
에 의존하고 있다. 하지만 이는 인터페이스가 아닌 구현체임.DI
는 인터페이스를 두고 구현체를 여러개 만들어 바꿔 사용하는 것이 목적이었음IoC
의 개념을 포괄하고 있으므로 DI
의 기본을 따르고 있다고 볼 수 있다.JdbcContext
를 DI
구조로 만들어야 할 이유JdbcContext
가 스프링 컨테이너의 싱글톤 레지스트리에서 관리되는 싱글톤 빈이 되기 때문!JdbcContext
가DI
를 통해 다른 빈에 의존하고 있기 때문!DI
를 위해서는 양쪽 클래스 모두 빈으로 등록되어야 한다!
UserDao
와 JdbcContext
가 매우 강하게 결합되어 있음.UserDao
내부에서 직접 DI
를 적용하는 방법!JdbcContext
를 갖고 있게 해야하기 때문이다.JdbcContext
에 대한 제어권을 스프링이 아닌 사용자가 갖고 생성과 관리를 담당하는 UserDao
에게 DI
까지 맡기는 것!UserDao
와 StatementStrategy
, JDbcContext
를 이용해 만든 코드는 일종의 전략 패턴이 적용된 것!템플릿/콜백 패턴
이라고 부른다.콜백은 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트를 말한다.
전략 패턴
의 전략과 달리 템플릿 / 콜백 패턴
의 콜백은 보통 단일 메소드 인터페이스를 사용한다. 이는 템플릿의 작업 흐름 중 특정 기능을 위해 한 번 호출되는 것이 일반적이기 때문이다.템플릿 / 콜백 패턴
에서는 매번 메소드 단위로 사용할 오브젝트를 새롭게 전달받는다는 것이 특징이며 또한 콜백 오브젝트가 내부 클래스로서 자신을 생성한 클라이언트 메소드 내의 정보를 직접 참조한다는 것도 고유한 특징이다.executeSql()
메소드를 JdbcContext
내부로 옮겨 여러곳에서 사용할 수 있도록 수정한다.템플릿 / 콜백
기능을 잘 사용할 수 있어야 한다.템플릿 / 콜백 패턴
의 후보는 try/catch/finally
블록을 사용하는 코드다.템플릿/콜백은 스프링이 객체지향 설계와 프로그래밍에 얼마나 가치를 두고 있는지를 잘 보여주는 예다! 우리는 이를 잘 사용하는 것은 물론이고 필요하면 직접 만들어서 활용할 수도 있어야 한다!
Author And Source
이 문제에 관하여([토비의 스프링] - 템플릿), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@ak2j38/토비의-스프링-템플릿저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)