템플릿 메소드 패턴
알고리즘의 골격을 정의한 메소드를 제공하고, 일부 단계를 서브클래스에서 구현하게 함으로써, 구조를 유지하면서도 특정 부분의 변경을 가능하게 하는 디자인 패턴.
구조
- AbstractClass - 서브클래스들이 재정의를 통해 구현해야 하는 알고리즘 처리 단계의 기본 연산(골격)을 정의한다. 그리고 알고리즘의 뼈대를 정의하는 템플릿 메서드를 구현한다.
- ConcreteClass - 서브클래스마다 달라진 알고리즘 처리 단계를 수행하기 위한 기본 연산을 구현한다.
기본 인터페이스
// 추상 기본 클래스
public abstract class AbstractClass
{
// 템플릿 메소드
public void TemplateMethod()
{
BaseOperation(); // 기본 작업 수행
RequiredOperation(); // 서브클래스에서 구현된 필수 작업 수행
OptionalHook(); // 서브클래스에서 재정의할 수 있는 선택적 후크 수행 (있을 경우)
}
private void BaseOperation()
{
Console.WriteLine("기본 작업 수행");
}
// 서브클래스에서 구현해야 하는 추상 메소드
protected abstract void RequiredOperation();
// 선택적인 후크 메소드 (기본적으로 비어있음)
protected virtual void OptionalHook()
{
}
}
// 구체 클래스
public class ConcreteClass : AbstractClass
{
protected override void RequiredOperation()
{
Console.WriteLine("ConcreteClass에서 구현된 필수 작업");
}
protected override void OptionalHook()
{
Console.WriteLine("ConcreteClass에서 구현된 선택적 후크");
}
}
// 메인 클래스
class Program
{
static void Main()
{
AbstractClass example = new ConcreteClass();
example.TemplateMethod(); // 템플릿 메소드 실행
// 출력:
// 기본 작업 수행
// ConcreteClass에서 구현된 필수 작업
// ConcreteClass에서 구현된 선택적 후크
}
}
왜 사용해야 할까?
템플릿 메소드 패턴의 핵심 아이디어는 알고리즘의 구조를 정의하고, 일부 단계를 서브클래스에서 구현하는 것이다. 이렇게 함으로써 알고리즘의 구조를 변경하지 않고도 특정 단계를 재정의할 수 있다.
게임 개발에서 템플릿 메소드 패턴은 게임 내 다양한 타입의 캐릭터 동작 구현에 유용하게 쓰일 수 있다.
모든 캐릭터는 '공격', '이동', '방어' 같은 기본적인 동작을 가지고 있지만, 각 캐릭터 별로 이러한 동작들이 다르게 구현될 수 있다. 추상 클래스에서는 이러한 동작들의 기본 흐름을 정의하고, 구체적인 캐릭터 클래스에서 각 동작의 세부 사항을 구현한다. 예를 들어, 전사와 마법사 클래스는 공격 방식이나 이동 방식이 다를 수 있다. 전사는 근접 공격을 하고, 마법사는 원거리 공격을 한다. 이런 차이점들은 각 서브클래스에서 구현된다.
AbstractClass (Character)
// 추상 기본 클래스
public abstract class Character
{
// 템플릿 메소드
public void PerformAction()
{
Move();
Attack();
Defend();
}
protected abstract void Move();
protected abstract void Attack();
protected abstract void Defend();
}
Character 클래스는 게임 내 모든 캐릭터들의 기본적인 행동 템플릿을 제공한다. 이 클래스에서는 PerformAction이라는 템플릿 메소드를 정의하고 있으며, 이 메소드는 Move, Attack, Defend라는 세 가지 추상 메소드를 순서대로 호출한다. 이러한 추상 메소드들은 각 캐릭터의 특정 동작을 나타내며, 실제 구현은 서브클래스에서 이루어진다.
ConcreteClass (Warrior와 Wizard)
// 구체 클래스 - 전사
public class Warrior : Character
{
protected override void Move()
{
Console.WriteLine("전사가 빠르게 이동합니다.");
}
protected override void Attack()
{
Console.WriteLine("전사가 칼로 공격합니다.");
}
protected override void Defend()
{
Console.WriteLine("전사가 방패로 방어합니다.");
}
}
// 구체 클래스 - 마법사
public class Wizard : Character
{
protected override void Move()
{
Console.WriteLine("마법사가 부유하며 이동합니다.");
}
protected override void Attack()
{
Console.WriteLine("마법사가 마법 주문으로 공격합니다.");
}
protected override void Defend()
{
Console.WriteLine("마법사가 마법 방패로 방어합니다.");
}
}
Warrior와 Wizard 클래스는 Character의 구체적인 구현이다. 각 클래스는 Move, Attack, Defend 메소드를 재정의하여, 전사와 마법사 각각의 독특한 이동, 공격, 방어 방식을 구현한다.
Warrior 클래스는 근접 공격과 방패 방어를, 반면 Wizard 클래스는 원거리 마법 공격과 마법 방패를 사용하는 방식으로 각 메소드를 구현한다. 이를 통해 같은 PerformAction 메소드 호출에도 불구하고, 각 캐릭터는 전혀 다른 방식으로 행동하게 된다.
Character warrior = new Warrior();
Character wizard = new Wizard();
Console.WriteLine("전사의 행동:");
warrior.PerformAction();
// 출력:
// 전사가 빠르게 이동합니다.
// 전사가 칼로 공격합니다.
// 전사가 방패로 방어합니다.
Console.WriteLine("\n마법사의 행동:");
wizard.PerformAction();
// 출력:
// 마법사가 부유하며 이동합니다.
// 마법사가 마법 주문으로 공격합니다.
// 마법사가 마법 방패로 방어합니다.
Warrior와 Wizard 인스턴스를 생성하고, 각각에 대해 PerformAction 메소드를 호출한다.
PerformAction은 템플릿 메소드 패턴을 사용하여 정의된 순서대로 Move, Attack, Defend 메소드를 실행한다. 전사와 마법사 클래스는 이 메소드들을 각각의 캐릭터에 맞게 재정의하고 있어, 동일한 메소드 호출에도 불구하고 각 캐릭터는 자신만의 고유한 방식으로 행동한다. 이를 통해 코드의 재사용성과 유지보수성을 높일 수 있다.
장점
- 템플릿 코드를 재사용하고 중복 코드를 줄일 수 있다.
- 템플릿 코드를 변경하지 않고 상속을 받아서 구체적인 알고리즘만 변경할 수 있다.
단점
- 리스코프 치환 원칙(LSP) 을 위반할 수도 있다.
- 알고리즈 구조가 복잡할 수록 템플릿을 유지하기 어려워진다.
템플릿 콜백
템플릿 메소드 패턴을 상속 대신 익명 내부 클래스 또는 람다 표현식을 활용할 수 있다. 이것을 템플릿 콜백이라 하는데, 콜백 함수(델리게이트, 람다, 함수 포인터 등)를 사용하여 템플릿 메소드의 각 단계를 구현한다. 이 접근법은 특히 다양한 언어에서 사용할 수 있으며, 특히 함수형 프로그래밍이나 람다 표현식을 지원하는 언어에서 유용하다.
콜백을 사용하여 템플릿 메소드 패턴을 구현하는 방식은 기본 클래스에서 구체적인 행동을 직접 정의하는 대신, 특정 행동을 수행하는 함수를 매개변수로 받는다. 이렇게 하면 구체적인 행동을 외부에서 정의하고, 필요에 따라 쉽게 변경할 수 있다.
public class TemplateMethodWithCallback
{
private readonly Action _moveAction;
private readonly Action _attackAction;
private readonly Action _defendAction;
public TemplateMethodWithCallback(Action moveAction, Action attackAction, Action defendAction)
{
_moveAction = moveAction;
_attackAction = attackAction;
_defendAction = defendAction;
}
public void PerformAction()
{
_moveAction();
_attackAction();
_defendAction();
}
}
class Program
{
static void Main()
{
var warriorActions = new TemplateMethodWithCallback(
() => Console.WriteLine("전사가 빠르게 이동합니다."),
() => Console.WriteLine("전사가 칼로 공격합니다."),
() => Console.WriteLine("전사가 방패로 방어합니다.")
);
var wizardActions = new TemplateMethodWithCallback(
() => Console.WriteLine("마법사가 부유하며 이동합니다."),
() => Console.WriteLine("마법사가 마법 주문으로 공격합니다."),
() => Console.WriteLine("마법사가 마법 방패로 방어합니다.")
);
Console.WriteLine("전사의 행동:");
warriorActions.PerformAction();
Console.WriteLine();
Console.WriteLine("마법사의 행동:");
wizardActions.PerformAction();
}
}
템플릿 콜백은 클래스 계층 구조를 사용하지 않고도 템플릿 메소드 패턴의 유연성을 제공한다. 콜백 함수는 람다 표현식이나 다른 메소드를 참조할 수 있어, 실행 시점에 행동을 쉽게 변경할 수 있다.
참고자료
'프로그래밍 이론 > 디자인 패턴' 카테고리의 다른 글
24. [Design Pattern] 방문자 패턴(Visitor) (0) | 2024.02.29 |
---|---|
22. [Design Pattern] 전략 패턴(Strate) (0) | 2024.02.27 |
21. [Design Pattern] 옵저버 패턴(Observer) (1) | 2024.02.26 |
20. [Design Pattern] 상태 패턴(State) (0) | 2024.02.20 |
19. [Design Pattern] 메멘토(Memento) 패턴 (0) | 2024.02.19 |