Template 메소드 패턴과 Callback 패턴에 대해 우선 따로따로 이해해보는 게 좋겠다 !
(이론 -> 구현 내용 순으로 구성하였습니다.)
이론
Template 메소드 패턴
(문제 상황) 핵심 기능 & 부가 기능이 모조리 섞여 있는 문제
(해결 방법) 변하는 것과 변하지 않는 것(반복되는 코드)을 분리하겠다 !
-> 상속으로 푼다.
이론보다 , 코드 샘플로 이해해보자!
AbstractTemplate (변하지 않는 부분, 반복되는 부분)
이 부분이 바로 Template, 우리가 아는 파워포인트 템플릿과 비슷하게 변하지 않는, 반복되는 부분에 해당한다.
package hello.advanced.trace.template.code;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class AbstractTemplate {
public void execute() {
long startTime = System.currentTimeMillis(); //비즈니스 로직 실행
call(); //상속으로 구현됨
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
protected abstract void call();
}
변하는 부분1
package hello.advanced.trace.template.code;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SubClassLogic1 extends AbstractTemplate {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
}
변하는 부분2
package hello.advanced.trace.template.code;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SubClassLogic2 extends AbstractTemplate {
@Override
protected void call() {
log.info("비즈니스 로직2 실행");
}
}
적용하는 부분
@Test
void templateMethodV1() {
AbstractTemplate template1 = new SubClassLogic1();
template1.execute();
AbstractTemplate template2 = new SubClassLogic2();
template2.execute();
}
위를 확인해보면 일일이 SubClassLogic1, SubClassLogic2 ..를 생성하는 문제가 있다.
직접 클래스를 만들지 않고 그냥 익명 내부 클래스를 사용해 문제를 해결하자.
@Test
void templateMethodV2() {
AbstractTemplate template1 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
};
log.info("클래스 이름1={}", template1.getClass());
template1.execute();
AbstractTemplate template2 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
};
log.info("클래스 이름2={}", template2.getClass());
template2.execute();
}
마무리
- 상속의 경우 T(제네릭)을 사용해서 추가로 구현할 수 있다. (제네릭에서 void만 Void로 return 해줘야 하는 것만 주의!)
- 이를 통해 SRP (단일 책임 원칙) 을 지킬 수 있다 !
- 한계가 있다면 상속!
자식 클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는데, 부모 클래스를 알아야 한다. 그리고 익명 내부 클래스도 만들어야 한다.ㅠㅠ
전략 패턴
상속보다는 구성(구현, implementation)을 사용한 방법이다 ! 템플릿 메소드 패턴의 상속이라는 한계를 극복할 수 있다.
변하지 않는 부분을 Context에, 변하는 부분을 Strategy에 넣는다.
긴 말 말고 코드로 확인해보자 !
변하는 부분 - Strategy
package hello.advanced.trace.strategy.code.strategy;
public interface Strategy {
void call();
}
이 Strategy 인터페이스를 구현한 것
package hello.advanced.trace.strategy.code.strategy;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class StrategyLogic1 implements Strategy {
@Override
public void call() {
log.info("비즈니스 로직1 실행");
}
}
ContextV1 필드에 전략 보관 - 변하지 않는 로직
Context는 Strategy 인터페이스에만 의존한다.
package hello.advanced.trace.strategy.code.strategy;
import lombok.extern.slf4j.Slf4j;
/**
* 필드에 전략을 보관하는 방식 */
@Slf4j
public class ContextV1 {
private Strategy strategy;
public ContextV1(Strategy strategy) {
this.strategy = strategy;
}
public void execute() {
long startTime = System.currentTimeMillis(); //비즈니스 로직 실행
strategy.call(); //위임
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
}
ContextV1에 나에게 맞는 Strategy 를 넣는다.
@Test
void strategyV1() {
Strategy strategyLogic1 = new StrategyLogic1();
ContextV1 context1 = new ContextV1(strategyLogic1);
context1.execute();
Strategy strategyLogic2 = new StrategyLogic2();
ContextV1 context2 = new ContextV1(strategyLogic2);
context2.execute();
}
실행과정
Context에 원하는 Strategy가 주입된다. -> 클라이언트는 Context를 실행한다. -> Context는 로직을 시작한다. -> Context 로직 중간에 strategy.call()이 호출되어 주입받은 strategy 로직을 실행한다. -> context는 나머지 로직을 실행한다.
마무리
- 람다와 함께 쓰는 경우가 많다.(Java8) - 인터페이스 메소드가 하나만 있을 때 사용 가능
- 선조립 후 실행
실행 전에 원하는 모양으로 조립해둔 후 context1.execute(); - Strategy(전략)을 Context 필드에 넣는다.
템플릿 콜백 패턴
템플릿 콜백 패턴은 사실상 전략패턴과 유사하다. 전략 패턴 + 익명 내부 클래스 로 이해해도 좋다. 콜백이란, 쉽게 호출(call)한 코드는 코드를 넘겨준 곳의 뒤(back)에서 실행된다는 의미이다.
이름만 Context -> Template , Strategy -> Callback으로 바뀐다.
@Test
void callbackV1() {
TimeLogTemplate template = new TimeLogTemplate();
template.execute(new Callback() {
@Override
public void call() {
log.info("비즈니스 로직1 실행");
}
});
template.execute(new Callback() {
@Override
public void call() {
log.info("비즈니스 로직2 실행");
}
});
//람다 사용
TimeLogTemplate template = new TimeLogTemplate();
template.execute(() -> log.info("비즈니스 로직1 실행"));
template.execute(() -> log.info("비즈니스 로직2 실행"));
}
execute 로직을 수행할 때 굳이 새로운 Context를 만들어 따로 contextv1.execute() 하지 않아도 된다.
반복되는, 변하지 않는 부분들은 뒤에서 알아서 (Callback하여) 해결해준다.
JDBC Template
JDBC를 이용하는 작업의 일반적인 순서는 아래와 같다.
그러면 여기에서 어떤 게 변하는 것이고 변하지 않는 것인지 파악하고, 템플릿과 콜백을 어떤 곳에 둘지를 생각해야 한다.
아마 변하는 부분은 연결하는 DB 종류, SQL문 정도 일 것 이다.
여기<=에서 템플릿 패턴을 적용해보았다.
처음에는 우선 Java Spring만을 이용해 온전히 DB에 connection을 맺고, JSONObject를 이용해 데이터를 가져오는 과정을 생으로 구현해본 후
다음으로는 변하는 부분과 변하지 않는 부분을 나누어주었다. 이 때 SQL(변하는 부분)은 template에서 parameter로 넘겨서 구현했기에 사실상 뒷단에서 실행되는 '콜백' 형태를 만족시키지는 않는다.
따라서 콜백형태와 구현(impl)을 적절히 활용해 Connection을 parameter로 받고, SQL은 구현과정에서 주입받아 구현하였다.
이 후 Override 가 하나만 있었기에 Lambda와 익명 내부 클래스를 활용해서 콜백 패턴을 마무리하였다!
JDBC Template 구현 내용
로직
Template을 실행하면서 JdbcCallback 을 전달한다. 아래의 UserCallback의 경우에는 람다나 익명 내부 클래스를 사용하지 않고 순수하게 JdbcCallback을 implements 하는 부분을 추가적으로 구현했다.
JdbcTemplate (변하지 않는 부분)
//** 변하는 부분 ** 에서 콜백을 수행한다.
package com.example.jdbctemplate.pattern;
import java.sql.*;
//변하지 않는 부분
public class JdbcTemplate {
private Connection getConnection(String url, String username, String password) throws SQLException{
return DriverManager.getConnection(url, username, password);
}
// SQL 쿼리 생성 및 실행
public void executeQuery(JdbcCallback callback, String url, String username, String password){
Connection conn = null;
PreparedStatement ps = null;
try {
conn = getConnection(url, username, password);
// ** 변하는 부분 - SQL 로직 수행 **
ps = callback.setPreparedStatement(conn);
ResultSet result = ps.executeQuery();
//단순한 출력 로직
while (result.next()){
System.out.println(result.getString(1));
}
} catch (SQLException e){
e.printStackTrace();
} finally {
closeConnection(conn);
}
}
// DB 연결 끊기
private void closeConnection(Connection conn){
if (conn != null) {
try {
conn.close();
} catch (SQLException e){
e.printStackTrace();
}
}
}
}
JdbcCallback
conn을 파라미터로 받아 여기에서 아예 DB에 SQL문을 수행하여 결과값을 가져온다.
package com.example.jdbctemplate.pattern;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
//변하는 부분
public interface JdbcCallback {
//SQL 문
//PreparedStatement 생성하는 부분 콜백!
public PreparedStatement setPreparedStatement(Connection conn) throws SQLException;
}
UserCallback
Lambda와 익명 내부 클래스로 구현할 수 있으나 한 번 추가적으로 전략패턴으로도 구현해보았다.
package com.example.jdbctemplate.pattern;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class UserCallback implements JdbcCallback {
@Override
public PreparedStatement setPreparedStatement(Connection conn) throws SQLException {
return conn.prepareStatement("SELECT * FROM USER");
}
}
테스트를 위한 실행 코드! 실행해보쟈-
package com.example.jdbctemplate.pattern;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class PatternMain {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/project";
String username = "root";
String password = "root";
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.executeQuery(new JdbcCallback(){
@Override
public PreparedStatement setPreparedStatement(Connection conn) throws SQLException {
return conn.prepareStatement("SELECT * FROM USER");
}
}, url, username, password);
}
}
결과
의도한 값이 아주 잘 출력되는 것을 볼 수 있다.! 쿼리를 바꾸어도 아주 잘 출력된다.
출처가 기억이 안나지만 어디에선가 디자인 패턴을 이론적으로만 공부한다면 자전거 타는 법을 책으로 배우는 것이다는 말을 보았다. 이 말에 자극받아서 스스로 과제하는 것처럼 미션수행(?)을 해봤는데 직접 이해한 내용을 통해 구현해보며 전략/템플릿 메소드 패턴이 손에 조금은 익은 것 같아 기쁘다✨. 정석적인 JDBC template은 아닐 수 있으나 '중복 코드 분리'에 집중해서 봐주면 될 것 같다. JDBC 템플릿을 통해 필요한 쿼리들을 일일히 객체로 구현하거나 파라미터로 전달하지 않아도 콜백할 수 있다는 점과, 반복되는 코드들이 한곳에 모여있다는 점들이 SPRING의 프레임워크적 장점으로 다가왔다. 앞으로 SPRING을 더 잘 쓰고 싶다 !! 😎
참고
'BackEnd > JAVA\SPRING' 카테고리의 다른 글
[Spring Security] Spring Security 이용하여 헤더로 간단히 User 구분하기 (0) | 2024.07.10 |
---|---|
[S3|AWS] S3와 CloudFront로 이미지 저장소 만들기 (0) | 2024.06.30 |
[SpringSecurity] Koala 프로젝트 간단한 인증처리 (0) | 2024.02.13 |
[Java|Spring] Koala 프로젝트 구현 과정 중 이슈 정리 (0) | 2024.02.01 |
[WAS/WS|Docker] Springboot war 파일 외장 tomcat 배포 (2) | 2024.01.10 |