Template Method
하위 클래스에서 구체적으로 처리하기
본 글은 'java 언어로 배우는 디자인 패턴 입문' 을 바탕으로 기재한 내용입니다.
템플릿이란 특정한 모양을 만들어진 틀을 말한다.
학교 다닐때 사용하던 플라스틱 자에 뚫려있는 특정 모양에 따른 구멍에 연필, 펜 등 을 사용하여 그리곤 했다.
템플릿의 구멍을 보면 어떤 모양인지는 알지만 실제로 어떤 모양이 될지는 필기구에 의해서 결정된다.
어떤 필기구를 사용하던 구멍의 형태는 동일하지만 말이다.
Template Method 패턴은 이러한 템플릿의 기능을 가진 패턴이다.
상위 클래스쪽에 템플릿에 해당하는 메소드가 정의되어 있고, 그 메소드 안에 추상 메소드가 사용되고 있다.
추상 메소드를 실제로 구현하는 것은 하위 클래스이다. 하위 클래스에서 서로 다른 구현을 실행할 수 있지만 어떤 구현을 하더라도 처리의 큰 흐름은 상위 클래스에서 결정한대로 이루어 지는 것이 특징이다.
예제 프로그램
문자나 문자열을 5회 반복해서 표시하기!!!
템플릿 만들기
public abstract class AbstractDisplay { // 추상 클래스 AbstractDisplay
public abstract void open(); // 하위클래스에 구현을 맡기는 추상 메소드(1) open
public abstract void print(); // 하위클래스에 구현을 맡기는 추상 메소드(2) print
public abstract void close(); // 하위클래스에 구현을 맡기는 추상 메소드(3) close
public final void display() { // 추상클래스에서 구현되고 있는 메소드 display
open(); // 우선 open 하고
for (int i = 0; i < 5; i++) { // 5번 print 하고
print();
}
close(); // close 한다 -> 이것이 display 메소드에서 구현되고 있는 내용.
}
}
open, print, close 는 추상 메소드이고 display 만 구현되어 있다.
open. print, close 추상메소드는 각각의 하위 클래스에서 다르게 구현할 수 있다.
문자 출력하기
public class CharDisplay extends AbstractDisplay {
private char ch;
public CharDisplay(char ch) {
this.ch = ch;
}
public void open() {
System.out.print("<<");
}
public void print() {
System.out.print(ch);
}
public void close() {
System.out.println(">>");
}
}
open, print, close 메소드는 각각 AbstractDisplay 로부터 오버라이드해서 정의한다.
문자열 출력하기
public class StringDisplay extends AbstractDisplay {
private String string;
private int width;
public StringDisplay(String string) {
this.string = string;
this.width = string.getBytes().length;
}
public void open() {
printLine();
}
public void print() {
System.out.println("|" + string + "|");
}
public void close() {
printLine();
}
private void printLine() {
System.out.print("+");
for (int i = 0; i < width; i++) {
System.out.print("-");
}
System.out.println("+");
}
}
생성자 : 문자열과 문자열의 바이트 단위 길이를 필드에 기억해둔다.
open, print, close 메소드는 각각 AbstractDisplay 로부터 오버라이드해서 정의한다.
실행
public class Main {
public static void main(String[] args) {
// 'H' 를 가진 CharDisplay 인스턴스를 1개 만든다.
AbstractDisplay d1 = new CharDisplay('H');
// "Hello, world."를 가진 StringDisplay의 인스턴스를 1개 만든다.
AbstractDisplay d2 = new StringDisplay("Hello, world.");
// "나는 프로그래머" 를 가진 StringDisplay의 인스턴스를 1개 만든다.
AbstractDisplay d3 = new StringDisplay("나는 프로그래머");
d1.display();
d2.display();
d3.display();
}
}
<<HHHHH>>
+-------------+
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
+-------------+
+----------------------+
|나는 프로그래머|
|나는 프로그래머|
|나는 프로그래머|
|나는 프로그래머|
|나는 프로그래머|
+----------------------+
바이트 단위로 하니까 마지막 한글은 길이가 맞지 않는다. ㄷㄷ length() 해도 마찬가지임
패턴의 등장인물
* AbstractClass(추상 클래스) 의 역할
AbstractClass는 템플릿 메소드를 구현한다. 그 템플릿 메소드에서 사용되고 있는 추상 메소드를 선언한다.
추상 메소드는 하위클래스(ConcreateClass) 에서 구현된다.
예제에서는 AbstractDisplay 클래스
* ConcreateClass(구현 클래스)의 역할
AbstractClass 역할에서 정의되어 있는 추상 메소드를 구체적으로 구현함.
예제에서는 CharDisplay, StringDisplay 클래스가 이 역할을 함
왜 Template Method 패턴?
상위 클래스의 템플릿 메소드에서 알고리즘이 기술되어 있으므로, 하위 클래스에서 알고리즘을 매번 기술 할 필요 없음
예를 들어 템플릿 메소드에서 복사&붙여넣기 기능을 구현하면 ConcreateClass1,2,3 ... 각각 수정할 필요가 없다.
Tip 상위 클래스와 하위 클래스의 연계
템플릿 메소드에서는 상위클래스와 하위클래스가 긴밀하게 연결되어 작동을 하고 있다
따라서 상위 클래스에서 선언된 추상 메소드를 실제로 하위 클래스에서 구현할때 어느 타이밍에 호출되는지 이해해야 어려움이 없다.
Tip 하위클래스를 상위 클래스와 동일시한다.
예제 에서는 CharDisplay의 인스턴스도, StringDisplay의 인스턴스도 AbstractDisplay형의 변수에 대입하고 있다.
그리고 display() 메소드를 호출하는 형태이다. 이처럼 instanceof 등으로 하위클래스의 종류를 특정하지 않아도 프로그램이 작동하게 만드는 것이 좋다.
"상위 클래스형의 변수에 하위 클래스의 어떠한 인스턴스를 대입해도 제대로 작동할 수 있도록 한다" 의 원칙은 The Liskov Subsitiution Principle(LSP) 라고 불린다. (이 원칙은 템플릿 메소드에 국한되지 않는 상속의 일반적인 원칙이다.)
관련 패턴
Factory Method 패턴 : 템플릿 메소드 패턴을 인스턴스 생성에 응용한 전형적인 예 이다.
Strategy 패턴 : 템플릿 메소드 패턴은 '상속'을 이용해서 프로그램의 동작을 변경한다. (상위 클래스에서 프로그램 큰 흐름 결정 후 하위 클래스에서 구체적인 흐름 결정) 반면에 Strategy 패턴은 '위임'을 이용해서 프로그램의 동작을 변경 할 수 있다. (Strategy 패턴에서는 프로그램의 일부분을 변경하기 보다 알고리즘 전체를 완전히 바꾼다!@)
보강
하위 클래스의 책임
클래스 계층에 대해 배울 때 대부분은 하위 클래스의 시점에서 생각하게 된다.
- 상위 클래스에서 정의도어 있는 메소드를 하위클래스에서 이용할수 있다
- 하위 클래스에 약간의 메소드를 기술해서 새로운 기능을 추가할 수 있다.
- 하위 클래스에서 메소드를 오버라이드하면 동작을 변경할 수 있다.
여기에서 시점을 바꿔 생각하면 상위클래스 입장에서는 이렇게 생각하게 될 것이다.
- 하위 클래스에서 메소드가 구현되길 기대한다.
- 하위 클래스에 대해서 그 메소드의 구현을 요청한다.
하위 클래스는 상위 클래스에서 선언된 추상 메소드를 구현할 책임이 생겼다고 말 할 수있으며, 이것을 subclass responsibility(하위 클래스의 책임) 이라고 한다.
상위 클래스와 하위 클래스의 협조
상위 클래스에서 기술을 많이 하면 하위 클래스에서는 기술하기 편하지만 자유는 줄어든다.
상위 클래스에서 기술을 적게 하면 하위 클래스에서는 기술이 어렵게 되거나 처리의 기술이 중복될 수 있다.
Template Method 패턴에서는 처리의 골격을 상위 클래스에서 기술하고 구체적인 내용은 하위 클래스에서 수행하고 있다. 어느 레벨에서 처리를 분배할지, 어떤 처리를 상위 클래스 혹은 하위 클래스에 둘 것인지의 판단은 설계하는 프로그래머의 몫이다.
예제에서 final 의 역할은?
하위 클래스에서 오버라이드(override) 할 수 없게 한다.
open, print, close 메소드를 상속, 동일 패키지에 있는 클래스에서만 호출하려면?
open, print, close 메소드를 protected 로 선언한다.
Java의 인터페이스는 추상 클래스와 비슷한데 Template Method 패턴에서 AbstractClass 역할에 인터페이스를 사용하지 못하는 이유는?
AbstractClass 역할은 처리의 골격(예를들어 템플릿 자 속의 여러 도형들)을 구현해야한다.
추상 클래스에서는 일부 메소드를 구체적으로 구현할 수 있는 반면에 인터페이스는 모든 메소드를 추상 메소드로 해야한다. 따라서 템플릿 메소드에서 인터페이스를 사용할 수 없다.