템플릿 메소드 패턴
템플릿 메소드 패턴이란?
GoF Design Patterns
에서는 템플릿 메소드 패턴을 다음과 같이 정의한다.
템플릿 메소드 패턴에서는 메소드에 알고리즘의 구조를 정의한다.
하위 클래스에서 알고리즘 구조의 변경없이 알고리즘을 재정의 하는 패턴이다.
알고리즘이 단계별로 나누어지거나, 같은 역할을 하는 메소드이지만 여러곳에서 다른 형태로 사용되는 경우 유용한 패턴이다.
- GoF Design Patterns
즉, 어떤 작업을 처리하는 부분(알고리즘의 단계)을 서브 클래스로 캡슐화해서 전체 일을 수행하는 구조는 바꾸지 않고 특정 단계에서 수행하는 내역을 바꾸는 패턴
이다.
클래스 다이어그램
다음은 기본적인 템플릿 메소드 패턴의 클래스 다이어그램이다.
이미지 출처 : https://www.crocus.co.kr/1531
AbstractClass (추상클래스)
- 템플릿 메소드를 정의하는 클래스
- 하위 클래스에 공통 알고리즘을 정의하고 하위 클래스에서 구현될 기능을 primitive 메소드 또는 hook 메소드로 정의하는 클래스
ConcreteClass (구현클래스 - 추상클래스가 아닌 클래스)
- 물려받은 primitive 메소드 또는 hook 메소드를 구현하는 클래스
- 상위 클래스에 구현된 템플릿 메소드의 일반적인 알고리즘에서 하위 클래스에 적합하게 primitive 메소드나 hook 메소드를 오버라이드하는 클래스
또한 AbstractClass에 구현되는 메소드는 세가지 종류가 있는데 다음과 같다.
- 공통된 역할을 수행하는 템플릿 메소드 (Template Method)
- 반드시 구현해야 하는 추상 메소드 (Primitive Method)
- 오버라이딩 해서 사용하는 훅 메소드 (Hook Method)
hook 메소드란?
hook 메소드는 Abstract Class에서 선언되는 메소드이긴 하지만 기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않은 메소드이다. 그렇기 때문에 서브클래스에서는 hook 메소드를 다양한 위치에서 오버라이드 하거나 무시 하고 넘어갈 수 있다.
템플릿 메소드 패턴 예시 (java)
아이스 아메리카노와 아이스 라떼를 제조하는 법으로 템플릿 메서드 패턴에 대해 알아본다.
아이스 아메리카노 | 아이스 카페라떼 |
---|---|
1. 에스프레소를 추출한다. 2. 에스프레소에 물을 넣는다. 3. 얼음을 넣는다. | 1. 에스프레소를 추출한다. 2. 에스프레소에 우유를 넣는다. 3. 얼음을 넣는다. |
위와 같은 두 제조법을 템플릿 메소드 패턴 없이 작성하면 다음과 같을 수 있다.
package TemplateMethodPattern;
// 아이스 아메리카노 클래스
public class IceAmericano {
public void makeIceAmericano() {
brewEspresso();
addWater();
addIce();
}
private void brewEspresso() {
System.out.println("에스프레소를 추출합니다.");
}
private void addWater() {
System.out.println("물을 넣습니다.");
}
private void addIce() {
System.out.println("얼음을 넣습니다.");
}
}
package TemplateMethodPattern;
// 아이스 라떼 클래스
public class IceLatte {
public void makeIceLatte() {
brewEspresso();
addMilk();
addIce();
}
private void brewEspresso() {
System.out.println("에스프레소를 추출합니다.");
}
private void addMilk() {
System.out.println("우유를 넣습니다.");
}
private void addIce() {
System.out.println("얼음을 넣습니다.");
}
}
위의 코드를 봤을 때 아이스 아메리카노의 제조법과 아이스 라떼의 제조법은 중복되는 코드가 많다. 따라서 추상화를 거쳐 클래스 다이어그램으로 나타내본다면 다음과 같다.
Coffee 클래스에 공통된 메소드를 두고 상속을 통해 자식 클래스에서 addWate()와 addMilk() 메소드를 정의했다.
하지만 여기서 다음과 같이 한단계 더 추상화를 할 수 있다.
addSomething() 이라는 Primitive Method를 만들어서 하위클래스에서 구현하도록 변경
여기에 그대로 사용해도 되고 오버라이딩해서 사용해도 되는 훅 메소드 추가해보면 다음과 같다.
테이크아웃 손님을 위한 wrapForTakeout 메소드 추가
위의 클래스 다이어그램을 토대로 구현한 코드는 다음과 같다.
package TemplateMethodPattern;
// 템플릿 메소드를 정의하는 추상클래스 Coffee
public abstract class Coffee {
// 템플릿 메소드 makeCoffee()
public void makeCoffee() {
System.out.println("커피 제작을 시작합니다.");
brewEspresso();
addSomething();
addIce();
wrapForTakeout();
}
// 공통 메소드 brewEspresso(), addIce()는 상위 클래스에서 구현
public void brewEspresso() {
System.out.println("에스프레소를 추출합니다.");
}
private void addIce() {
System.out.println("얼음을 넣습니다.");
}
// 상속 시 반드시 구현해야하는 추상메소드 addSomthing()
public abstract void addSomthing();
// 선택적으로 오버라이딩하거나 무시할 수 있는 훅 메소드 wrapForTakeout()
public void wrapForTakeout() {
System.out.println("커피를 컵에 담습니다.");
}
}
package TemplateMethodPattern;
// 아메리카노 클래스
public class IceAmericano extends Coffee {
// 추상메소드 구현
public void addSomthing() {
System.out.println("물을 넣습니다.");
}
}
package TemplateMethodPattern;
// 라떼 클래스
public class IceLatte extends Coffee {
// 추상메소드 구현
public void addSomthing() {
System.out.println("우유를 넣습니다.");
}
}
package TemplateMethodPattern;
// 아메리카노 테이크아웃 클래스
public class Takeout_IceAmericano extends Coffee {
// 추상메소드 구현
public void addSomthing() {
System.out.println("물을 넣습니다.");
}
// 훅 메소드 오버라이딩
public void wrapForTakeout() {
System.out.println("커피를 포장합니다.");
}
}
package TemplateMethodPattern;
// 라떼 테이크아웃 클래스
public class Takeout_IceLatte extends Coffee {
// 추상메소드 구현
public void addSomthing() {
System.out.println("우유를 넣습니다.");
}
// 훅 메소드 오버라이딩
public void wrapForTakeout() {
System.out.println("커피를 포장합니다.");
}
}
package TemplateMethodPattern;
// 주문
public class Clerk {
public static void main(String[] args){
IceAmericano americano = new IceAmericano();
IceCafelatte cafelatte = new IceCafelatte();
Takeout_IceAmericano to_americano = new Takeout_IceAmericano();
Takeout_IceLatte to_latte = new Takeout_IceLatte();
// 아메리카노, 라떼 제조
System.out.println("--- Americano --- ");
americano.makeCoffee();
System.out.println("--- Latte ---");
cafelatte.makeCoffee();
// 아메리카노, 라떼를 제조 후 테이크 아웃
System.out.println("--- Takeout Americano --- ");
to_americano.makeCoffee();
System.out.println("--- Takeout Latte ---");
to_cafelatte.makeCoffee();
}
}
--- Americano ---
에스프레소를 추출합니다.
물을 넣습니다.
얼음을 넣습니다.
커피를 컵에 담습니다.
--- Latte ---
에스프레소를 추출합니다.
우유를 넣습니다.
얼음을 넣습니다.
커피를 컵에 담습니다.
--- Takeout Americano ---
에스프레소를 추출합니다.
물을 넣습니다.
얼음을 넣습니다.
커피를 포장합니다.
--- Takeout Latte ---
에스프레소를 추출합니다.
우유를 넣습니다.
얼음을 넣습니다.
커피를 포장합니다.
템플릿 메소드의 장단점
장점
- 중복 코드를 줄일 수 있다.
- 자식 클래스의 역할을 줄여 핵심 로직의 관리가 용이하다.
- 코드를 더 객체지향적으로 구성 할 수 있다.
단점
- 추상 메소드가 많아지면서 클래스 관리가 복잡해진다.
- 클래스간의 관계와 코드가 꼬여버릴 염려가 있다.
면접에 나올 수 있는 질문
Q. 디자인 패턴 중 템플릿 메소드 패턴이란?
A. 템플릿 메소드 패턴은 알고리즘의 특정 부분을 서브 클래스로 캡슐화해서 전체 일을 수행하는 구조를 바꾸지 않고 특정 단계에서 수행하는 내역을 바꾸는 패턴이다.
Q. 템플릿 메소드 패턴은 어느 상황에 유용한 패턴인가?
A. 알고리즘이 단계별로 나누어지거나 같은 역할을 하는 메소드가 여러 곳에서 다른 형태로 사용되는 경우 유용한 패턴이다.
Q. 템플릿 메소드 패턴은 어떤 구성으로 이루어지는가?
A. Absctrct Class (추상클래스) 와 Concrete Class (구현클래스)로 구성되며 Abstraction Class는 템플릿 메소드, 추상 메소드, 훅 메소드의 세 가지 종류의 메소드로 구성된다.
Q. 템플릿 메소드 패턴에서 추상클래스를 상속받는 하위 클래스를 정의할 때, 추상 메소드와 훅 메소드를 필수적으로 구현해야 하는가?
A. 추상클래스에 정의된 추상메소드는 하위 클래스에서 반드시 구현되어야 하지만, 훅 메소드는 필요에 따라 사용하지 않는 경우도 있으므로 오버라이딩이 필수가 아니다.
참고
- 디자인패턴 - 템플릿 메소드 패턴
- 템플릿 메서드 패턴(Template Method Pattern)
- 팩토리 메서드, 템플릿 메서드 패턴
- [Design Pattern] 템플릿 메소드 패턴(Template Method Pattern)에 대하여
기여자
Jongminfire
📦