템플릿 메서드 패턴(Template Method Pattern)
템플릿 메서드(Template Method) 패턴은 여러 클래스에서 공통으로 사용하는 메서드를 템플릿화 하여 상위 클래스에서 정의하고, 하위 클래스마다 세부 동작 사항을 다르게 구현하는 패턴이다.
즉, 변하지 않는 기능(템플릿)은 상위 클래스에 만들어두고 자주 변경되며 확장할 기능은 하위 클래스에서 만들도록 하여, 상위의 메소드 실행 동작 순서는 고정하면서 세부 실행 내용은 다양화될 수 있는 경우에 사용된다.
GoF 디자인 패턴에서는 템플릿 메서드 패턴을 다음과 같이 정의했다.
템플릿 메서드 디자인 패턴의 목적은 다음과 같습니다.
작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다.
템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의 할 수 있습니다.
템플릿 메서드 패턴 구조
- AbstractClass(추상 클래스) : 템플릿 메소드를 구현하고, 템플릿 메소드에서 돌아가는 추상 메소드를 선언한다. 이 추상 메소드는 하위 클래스인 ConcreteClass 역할에 의해 구현된다.
- ConcreteClass(구현 클래스) : AbstractClass를 상속하고 추상 메소드를 구체적으로 구현한다. ConcreteClass에서 구현한 메소드는 AbstractClass의 템플릿 메소드에서 호출된다.
템플릿 메서드 패턴으로 게임하기
템플릿 메서드 패턴의 예시로 게임을 플레이 하는 것을 코드로 구현해 본다.
우리는 FPS, RPG, AOS 게임을 구현할 것이다. 하지만 전체적인 골격은 아래와 같을 것이다.
로그인을 한다. -> 게임을 킨다. -> 게임을 플레이한다. -> 게임을 끈다. -> 로그아웃을 한다.
하지만 FPS, RPG, AOS 게임은 각각 플레이하는 방식이 다르다. 때문에 전체적인 골격은 같지만 어떤 게임을 플레이하냐에 따라 구현이 달라질 것이다.
템플릿 메서드 패턴이 없다면
RPGGame.java
public class RPGGame {
public void play() {
login();
turnOn();
killMonster();
turnOff();
logout();
}
private void logout() {
System.out.println("로그 아웃을 한다.");
}
private void login() {
System.out.println("로그인을 한다.");
}
private void turnOff() {
System.out.println("게임을 끈다.");
}
private void killMonster() {
System.out.println("몬스터를 잡는다.");
}
private void turnOn() {
System.out.println("게임을 킨다.");
}
}
FPSGame.java
public class FPSGame {
public void play() {
login();
turnOn();
shoot();
turnOff();
logout();
}
private void logout() {
System.out.println("로그 아웃을 한다.");
}
private void login() {
System.out.println("로그인을 한다.");
}
private void turnOff() {
System.out.println("게임을 끈다.");
}
private void shoot() {
System.out.println("총을 쏜다.");
}
private void turnOn() {
System.out.println("게임을 킨다.");
}
}
AOSGame.java
package template_method.before;
public class AOSGame {
public void play() {
login();
turnOn();
destroyTower();
turnOff();
logout();
}
private void logout() {
System.out.println("로그 아웃을 한다.");
}
private void login() {
System.out.println("로그인을 한다.");
}
private void turnOff() {
System.out.println("게임을 끈다.");
}
private void destroyTower() {
System.out.println("타워를 부순다.");
}
private void turnOn() {
System.out.println("게임을 킨다.");
}
}
3개의 클래스에서 다르게 구현된 것은 어떻게 플레이할지만 다르다. 때문에 플레이에 관한 부분을 제외한 코드는 중복이 일어나게 된다.
템플릿 메서드 패턴으로 중복 제거하기
이제 게임 플레이에 대한 전체적은 골격은 추상 클래스를 통해서 구현하고 어떻게 플레이할지에 관한 내용은 하위 클래스로 분리해 보자.
Game.java
abstract class Game {
public void play() {
login();
turnOn();
playGame();
turnOff();
logout();
}
private void logout() {
System.out.println("로그 아웃을 한다.");
}
private void login() {
System.out.println("로그인을 한다.");
}
private void turnOff() {
System.out.println("게임을 끈다.");
}
private void turnOn() {
System.out.println("게임을 킨다.");
}
public abstract void playGame();
}
playGame()
추상 메서드의 구현은 하위 클래스에게 위임하고 공통적인 로직만을 구현한다.
RPGGame.java
public class RPGGame extends Game{
@Override
public void playGame() {
System.out.println("몬스터를 잡는다.");
}
}
FPSGame.java
public class FPSGame extends Game{
@Override
public void playGame() {
System.out.println("총을 쏜다.");
}
}
AOSGame.java
public class AOSGame extends Game{
@Override
public void playGame() {
System.out.println("타워를 부순다.");
}
}
위와 같이 템플릿 메서드 패턴을 사용하게 되면, 동일한 실행 과정의 구현을 제공하면서 하위 클래스에서 일부 단계를 구현하도록 할 수 있다. 따라서 불필요한 중복을 해소할 수 있다.
템플릿 메서드 패턴 특징
패턴 사용 시기
- 클라이언트가 알고리즘의 특정 단계만 확장하고, 전체 알고리즘이나 해당 구조는 확장하지 않도록 할 때
- 동일한 기능은 상위 클래스에서 정의하면서 확장, 변화가 필요한 부분만 하위 클래스에서 구현할 때
패턴 장점
- 클라이언트가 대규모 알고리즘의 특정 부분만 재정의하도록 하여, 알고리즘의 다른 부분에 발생하는 변경 사항의 영향을 덜 받도록 한다.
- 상위 추상클래스로 로직을 공통화하여 코드의 중복을 줄일 수 있다.
- 서브 클래스의 역할을 줄이고, 핵심 로직을 상위 클래스에서 관리함으로써 관리가 용이해진다.
- 할리우드 원칙 (Hollywood Principle) : 고수준 구성요소에서 저수준을 다루는 원칙 (추상화에 의존)
패턴 단점
- 알고리즘의 제공된 골격에 의해 유연성이 제한될 수 있다.
- 알고리즘 구조가 복잡할수록 템플릿 로직 형태를 유지하기 어려워진다.
- 추상 메소드가 많아지면서 클래스의 생성, 관리가 어려워질 수 있다.
- 상위 클래스에서 선언된 추상 메소드를 하위 클래스에서 구현할 때, 그 메소드가 어느 타이밍에서 호출되는지 클래스 로직을 이해해야 할 필요가 있다.
- 로직에 변화가 생겨 상위 클래스에서 수정할 때, 모든 서브 클래스의 수정이 필요할 수도 있다.
- 하위 클래스를 통해 기본 단계 구현을 억제하여 리스코프 치환 법칙을 위반 할 여지가 있다.
참고 자료 :
'백엔드 > Java' 카테고리의 다른 글
[Java] 옵저버 패턴(Observer Pattern) (1) | 2023.05.17 |
---|---|
[Java] 전략 패턴(Strategy Pattern) (0) | 2023.05.17 |
[Java] 스트림(Stream) (0) | 2023.05.11 |
[Java] 람다(Lambda) (0) | 2023.05.09 |
[Java] 동작 파라미터화(Behavior Parameterization) (0) | 2023.05.09 |