본문 바로가기
공부/Spring

템플릿 콜백 패턴

by 무심한고라니 2021. 6. 21.

토비의 스프링을 보다가 템플릿/콜백 패턴을 접하게 되었습니다. 책에서는 이에 대해 다음과 같이 정의합니다.

 

조금 복잡해보이지만 메소드 레벨에서 일어나는 DI다. 왜냐하면 클라이언트가 템플릿 메소드를 호출하면서 콜백 오브젝트를 전달하는 것이기 때문이다.

 

DI, 템플릿 메소드, 콜백 오브젝트 등 여러 용어가 등장해 복잡해 보입니다. 이를 이해하기 위해 아래 내용[1]을 짚고 넘어가고자 합니다.

 

1. 전략 패턴

2. 템플릿 메소드 패턴

3. 템플릿 콜백 패턴

 

혹시 내용에 틀린 부분이나 피드백이 있다면 댓글로 남겨주시면 감사하겠습니다.

 

_____

전략 패턴

 

전략 패턴(Strategy Pattern)은 전략을 쉽게 바꿀 수 있도록 해주는 디자인 패턴이다. 아래 클래스 다이어그램을 살펴보자.

 

 

Context(문맥) 밖으로 Strategy(전략)를 분리해내 필요에 따라 동적으로 바꿀 수 있다. 이를 위해 Context 클래스에는 Strategy 타입의 필드와 setter 메소드가 존재한다. 즉 변경 가능한 코드(Strategy)에 대해 클래스 단위의 분리를 적용했다고 생각할 수 있다.

 

한편 스프링 프레임워크를 IoC(Inversion of Control) 혹은 DI(Dependency Injection) 컨테이너라고 부르는데 이때 런타임 시에 사용[2] 의존관계를 맺을 오브젝트를 주입해주는 기술인 DI는 전략 패턴과 같은 방식[3]으로 동작한다.

 

 

템플릿 메소드 패턴

 

개발자는 코드의 중복을 최소화해야 한다. 따라서 작성된 코드에서 반복적으로 보일러 플레이트 코드가 등장한다면 우리는 이를 따로 분리하여 중복을 최소화해야 한다. 템플릿 메소드 패턴은 상속을 통해 이러한 중복을 최소화해주는 패턴이다. UML을 살펴보자.

 

 

말 그대로 전체적인 로직은 상위 클래스에 정의(templateMethod)하면서 확장, 변화가 필요한 부분만 서브 클래스에서 구현(primitiveOperation1, primitiveOpertation2)할 수 있도록 한다[4]. 따라서 전체적인 로직을 재사용하는 데 유용한데, 토비의 스프링에서는 DAO 클래스 예시를 든다. 아래 UML을 살펴보자.

 

 

보면 DB에 입력, 조회하는 add, get 메소드가 템플릿 메소드다. 이 메소드들은 커넥션을 이용해서 입력 및 조회 처리를 하므로 공통 부분을 getConnection 메소드로 추출한다. 그리고 getConnection 메소드를 서브클래스에서 구현하도록 함으로써 UserDao 클래스에서 DB 커넥션 연결이라는 관심사를 분리했다. 이제 OCP[5]를 지킬 수 있게 되었다.

 

 

템플릿 콜백 패턴

 

앞서 언급한 두 패턴을 적용함으로써 결과적으로 객체지향의 설계 원칙인 OCP를 준수하게 되었다. 즉 확장에는 열려 있고 변경에는 닫혀 있는 코드 구조를 갖게 되었다. 그런데 상속, 합성 두 가지 이외에 또 뭐가 더 있는 걸까? 토비의 스프링의 예제를 직접 살펴보자[6].

 

public class Calculator {
	public Integer calcSum(String filepath) throws IOException {
		LineCallback sumCallback = new LineCallback() {
			@Override
			public Integer doSomethingWithLine(String line, Integer value) {
				return value + Integer.valueOf(line);
			}
		};
		return lineReadTemplate(filepath, sumCallback, 0);
	}

	public Integer calcMultiply(String filepath) throws IOException {
		LineCallback multiplyCallback = new LineCallback() {
			@Override
			public Integer doSomethingWithLine(String line, Integer value) {
				return value * Integer.valueOf(line);
			}
		};
		return lineReadTemplate(filepath, multiplyCallback, 1);
	}

	private Integer lineReadTemplate(String filepath, LineCallback callback, int initVal) throws IOException {
		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader(filepath));
			Integer res = initVal;
			String line = null;
			while ((line = br.readLine()) != null) {
				res = callback.doSomethingWithLine(line, res);
			}
			return res;
		} catch (IOException e) {
			System.out.println(e.getMessage());
			throw e;
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (IOException e) {
					System.out.println(e.getMessage());
				}
			}
		}
	}
}

public interface LineCallback {
	Integer doSomethingWithLine(String line, Integer value);
}

 

파일을 읽어들여 간단한 계산 기능을 수행하는 Calculator 클래스다. 위 코드의 경우 클래스 단위의 분리를 하진 않았지만 앞서 언급했던 템플릿 메소드 패턴과 비교해보면 getConnection 메소드[7]의 경우 서브클래스에서 특정 DB에 의존하는 Connection을 반환한다. UML로 보면 아래와 같다.

 

 

하지만 DB 커넥션과 달리 계산기의 경우나, 토비의 스프링에서 또 다른 예로 언급한 쿼리문 생성의 경우 하나하나 클래스로 생성하기에 그 수가 많고 재사용성도 적어 비효율적이다. 따라서 이런 경우 위와 같이 클라이언트(calcSum, calcMultiply)에서 익명 객체 생성[8] 후 템플릿 메소드(lineReadTemplate)에 콜백 오브젝트[9]를 매개변수로 넘겨주는 것이 효율적이다. 

 

작성 중

 

_____

1. 켄트 벡은 컴퓨터 과학에서 모든 문제에 대한 해결책이 인다이렉션 계층을 하나 더 만드는 것이라 말합니다. 그리고 자바에서는 상속과 합성으로 이를 구현할 수 있습니다.

2. Inheritance (IS-A) vs. Composition (HAS-A) Relationship

3. What is the difference between Strategy pattern and Dependency Injection?

4. 런타임 오브젝트 의존관계 다이어그램을 보면 아래와 같다.

5. Open-Closed Principle(개방-폐쇄 원칙)

6. 템플릿 콜백 패턴을 적용하기 전 코드는 다음과 같다.

public class Calculator {
	public Integer calcSum(String filepath) throws IOException {
		BufferedReader br = null;
		
		try {
			br = new BufferedReader(new FileReader(filepath));
			Integer sum = 0;
			String line= null;
			while((line = br.readLine()) != null) {
				sum += Integer.valueOf(line);
			}
			
			return sum;
		} catch (IOException e) {
			System.out.println(e.getMessage());
			throw e;
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (IOException e) {
					System.out.println(e.getMessage());
				}
			}
		}
	}
}

7. 리턴 타입으로 Connection 인터페이스를 가진다.

8. 익명 객체를 통해 코드의 양을 줄였다고는 하지만 보일러 플레이트 코드도 보이고, 가독성도 조금 떨어진다. 람다.

9. 일급 객체.

 

_____

참고자료

 

  • 토비의 스프링 3.1, 이일민 지음
  • JAVA 객체지향 디자인 패턴, 정인상/채홍석 지음
  • Design Patterns/Behavioral Patterns] Strategy
  • LichKing] Template Callback Pattern
  •  

_____

더 나아가

 

'공부 > Spring' 카테고리의 다른 글

스프링 핵심 원리 - 기본편  (0) 2021.01.18

댓글