이번에 정리 할 패턴은 템플릿 메소드 패턴입니다.
이 패턴은 팩토리패턴이나 싱글턴 패턴과 함께 일반적으로 상당히 많이 사용되는 패턴입니다.
템플릿이라는 그 이름 그대로 상위 클래스에서 알고리즘을 규약하고 일부 공통된 메소드를 직접 구현하여
가지고 있으며, 하위 클래스에서 처리 해야 할 메소드를 추상 메소드로 가지고 있습니다.
한번 예제를 들어보겠습니다.
검색 엔진에서는 검색을 할 수 있도록 데이터 소스에 접근하여
데이터를 읽어오고, 이를 색인하고 최적화의 단계를 거쳐 색인 파일을 완성합니다.
그런데 이 데이터 소스라는 것이
DB일수도 있고, 파일들 일 수도 있을 것입니다.
아무튼, 데이터 소스가 다르고 파일을 읽어오는 것이 다르다는 것만 제외하면
아래와 같은 절차를 가질 것 입니다.
1. 데이터소스에 접근한다.
2. 데이터를 읽어온다.
3. 색인을 실시한다.
4. 최적화를 실시한다.
그래서 우선 각각 두개의 클래스를 만들어보겠습니다.
public void connectDataBase() {
System.out.println("데이터베이스에 접속");
}
public void selectData() {
System.out.println("데이터를 셀렉트한다.");
}
public void indexing() {
System.out.println("색인실시");
}
public void optimize() {
System.out.println("최적화 실시");
}
}
---------------------------------------------------------
public class FileIndexing {
public void connectFileServer() {
System.out.println("파일서버에 접속");
}
public void readFile() {
System.out.println("파일을 읽는다.");
}
public void indexing() {
System.out.println("색인실시");
}
public void optimize() {
System.out.println("최적화 실시");
}
}
클래스를 보시면 아시겠지만 색으로 처리가 되어있는 녀석들은 중복이 되는 부분입니다.
DB건 파일서버건 상관없이 일단 데이터를 가져온 후에는 똑같이 색인과 최적화를 수행하죠.
그렇다면, 나머지 두개의 메소드는 어떨까요?
파일서버와 DB에 접속하여 데이터를 가져오는 것이지만.. 이 메소드 자체를 조금 추상화 시켜버리면
connectDataSource와 readData 정도로 추상화 시킬 수 있을 것입니다.
대상과 방법이 조금 다를뿐이지, 일이 진행되는 흐름에 있어서
그 내용 자체가 크게 다른 것은 아닙니다.
이렇게 추상화된 메소드를 사용해서 템플릿 메소드 패턴으로 구현해보겠습니다.
connectDataSource();
readData();
indexing();
optimize();
}
abstract void connectDataSource();
abstract void readData();
void indexing() {
System.out.println("색인을 실시한다.");
}
void optimize() {
System.out.println("최적화 실시");
}
void hook() {
}
}
이것이 추상화 시킨 클래스입니다.
분홍색으로 표시된 부분이 바로 템플릿 메소드입니다.
색인 절차에 대한 템플릿을 지정하고 final로 선언하여 하위 클래스에서 재정의 할 수 없도록 만들어져있습니다.
이 클래스를 상속하여 구현하는 클래스들은 반드시 저 템플릿 메소드에서 지정되어있는 순서에 따라서
작동합니다. 일종의 프레임워크인 셈입니다.
그리고 추상 메소드가 두개있습니다.
이것이 바로 하위 클래스에서 자기 자신이 할 일에 맡게 오버라이드 해줘야 하는 메소드입니다.
그리고 색인과 최적화는 어느 클래스에서도 공통으로 사용되므로, 상위 클래스에서 구현하고 있습니다.
public class DBIndexing extends DataIndexing {
@Override
public void connectDataSource() {
System.out.println("데이터베이스에 접속");
}
@Override
public void readData() {
System.out.println("데이터를 셀렉트한다.");
}
}
-----------------------------------------------------------
public class FileIndexing extends DataIndexing {
@Override
public void connectDataSource() {
System.out.println("파일서버에 접속");
}
@Override
public void readData() {
System.out.println("파일을 읽는다.");
}
}
이런식으로 하위클래스가 구현됩니다.
사용하는측에서는
dataIndexing.indexingSequence();
이렇게 사용하면 되겠죠. 그럼 지정된 절차에 의해서 메소드가 동작 할 것입니다.
위에서 상위 클래스를 보면 hook()라는 메소드가 있습니다.
이것은 필요에 따라서 하위클래스에서 구현해도 되고 하지 않아도 되는 메소드입니다.
템플릿 메소드 자체에 들어가서 영향을 주지는 않지만,
하위클래스에서 알고리즘을 구현하는데 조금 유연성을 줄 수 있습니다.
예를 들어서 최적화를 실시하는데 부하가 오기 때문에 무조건 최적화를 시키는 것이 아니라 옵션에 따라서 실시를 한다.. 라고 하면.. 템플릿 메소드를 이렇게 고칠 수도 있을 것입니다.
final void indexingSequence() {
connectDataSource();
readData();
indexing();
if(doOptimize()) {
optimize();
}
}
abstract void connectDataSource();
abstract void readData();
void indexing() {
System.out.println("색인을 실시한다.");
}
void optimize() {
System.out.println("최적화 실시");
}
boolean doOptimize() {
return true;
}
}
doOptimize는 무조건 true를 리턴하도록 되어있습니다.
하위에서 오버라이드를 하지 않으면 optimize까지 실시합니다. 하위에서 오버라이드해서 특정 조건을 만족했을 때만
(뭐 현재 사용량을 보거나.. 시간을 확인하거나..등등)
true를 리턴하도록 오버라이드 할 수도 있을 것입니다.
이런식으로, 제공되는 메소드가 hook 메소드입니다.
이 hook메소드가 상위 클래스에서 제공되는 이유는 모두 아시겠지만..
하위 클래스에서 그냥 쑥 나와버리면..
dataIndexing.indexingSequence();
이 패턴에서 가장 중요한 부분은 템플릿 메소드가 알고리즘의 골격을 정의하고 있다는 점입니다.