템플릿 메서드 패턴이란 “소프트웨어 공학에서 동작 상의 알고리즘의 프로그램 뼈대를 정의하는 행위 디자인 패턴이다. 알고리즘의 구조를 변경하지 않고 알고리즘의 특정 단계들을 다시 정의할 수 있게 해준다.” 라고 위키백과에서 정의하고있다.
좀더 개발자스럽게 표현한다면 다음과 같다.
- 메소드에서 알고리즘의 골격을 정의한다.
- 알고리즘의 여러 단계 중, 일부는 서브클래스에서 구현할 수 있다.
- 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의 할 수 있다.
자바를 공부하면 누구나 배우는 추상클래스를 가지고 클래스의 상속, 오버라이드등의 기본기능을 좀더 고급스럽게 사용한다는 느낌이다.
내가 처음 템플릿 메서드 패턴을 접한것은 어느 책인지는 기억이 잘 안나지만 내용은 JDBC를 통해 SQL에 접속하여 데이터를 가저오는 코드를 리팩토링하는 과정에서 이러한 중복 코드를 템플릿 메서드 패턴을 사용하여 리팩토링 한다는 내용이였다.
아래 그림은 템플릿 메서드 패턴에 대한 UML이다.
< 출처:위키백과(https://ko.wikipedia.org) >
내용은 간단하다. 추상클래스에 는 3개의 메서드가 존재하는데 상속가능한 primitive1(), primitive2() 2개의 메서드와 상속 불가한 templateMethod()가 존재한다. templageMethod()메서드는 primitive1(),primitive2() 를 사용해서 알고리즘(수행순서,제약 등.) 이 정의되어있고 이 알고리즘은 변하지않고 primitive1(),primitive2()는 구현 하는 서브클래스에 맞겨놓는 방식이다. 그렇지만 너무 이 내용에 집착(?)하면 명확한 알고리즘 순서가 있을때 템플릿 메소드 패턴을 사용한다고 오해(제한) 할 수 있음을 주의하라는 글을 본적있다. 즉, 단지 관심사를 분리하거나 코드중복을 줄이거나 혹은 전 처리나 후 처리등을 위해서 사용하는 경우도 많이 볼 수 있다. 또한, 처리흐름이 있더라도 처리흐름을 따로 분리해서 메서드를 만드는 경우도 많지 않다. 예를 들어 스프링의 AbstractHandlerMethodExceptionResolver 의 doResolveException 메서드에서 doResolveHandlerMethodException 추상메서드를 호출하는데 호출하는이유가 단지 형변한을 위한 전처리를 작업을 위해 템플릿 메서드 패턴을 사용했다.
디자인 패턴을 공부하다보면 “헐리우드 원칙” 이라는 용어가 등장한다.
먼저 연락하지 마세요. 저희가 연락 드리겠습니다. - 헐리우드 원칙
의존성 부패(dependency rot)를 방지하기 위한 디자인 패턴 원칙이고 템플릿 메서드에도 적용 된다. 즉, “구현 클래스에 메서드를 구현하면 그 사용(호출)은 추상클래스의 메서드에서 알아서 할테니 신경(접근불가)쓰지마” 이런 느낌?.
템플릿 메서드 패턴 설명에 나오는 전형적인 예시들.
차끓이기
- 물을 붓는다.
- 물을 끓인다.
- …
로그인 처리
- 보안처리
- 인증처리
- …
추상소켓서버
- 서버를 뛰운다.
- 접속을 대기한다.
- 입력을 받는다.
- …
그럼 템플릿 메서드 패턴은 스프리의 어디에서 어떻게 사용되고있는지 좀더 찾아보자
- java.io.InputStream 의 read() 메서드
public abstract int read() throws IOException; // 서브클래스에 구현 위임
// BufferedInputStream, ByteArrayInputStream 등의 다양한 클래스에 구현되어있다.
public int read(byte b[], int off, int len) throws IOException {
Objects.checkFromIndexSize(off, len, b.length);
if (len == 0) {
return 0;
}
int c = read(); //read() 호출부
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read(); //read() 호출부
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
- java.util.Collections의 sort() - 추상클래스가 아닌 Interface 의 Default Method 방식
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
//List 인터페이스의 default Method 는 listIterator() 를 호출함
// 이부 분은 다른 구현클래스(ex. ArraysList 등 )에서 정의됨
public interface List<E> extends Collection<E> {
.....
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
//listIterator 를 호출함
for (Object e : a) {
i.next();
i.set((E) e);
}
}
.....
}