2022. 3. 18. 00:00ㆍCSharp/Design Pattern
슈퍼클래스에서 알고리즘의 골격을 정의하지만 서브클래스가 구조를 변경하지 않고 알고리즘의 특정 단계를 재정의할 수 있도록 하는 행동 디자인 패턴
문제점
회사 문서를 분석하는 데이터 마이닝 응용 프로그램을 만들고 있다고 상상해 보십시오. 사용자는 다양한 형식(PDF, DOC, CSV)의 앱 문서를 제공하고 이러한 문서에서 일관된 형식으로 의미 있는 데이터를 추출하려고 합니다.
회사 문서를 분석하는 데이터 마이능 프로그램을 작성한다고 하자.
앱의 첫 번째 버전은 DOC 파일에서만 작동할 수 있다.
다음 버전에서는 CSV 파일을 지원할 수 있다.
한 달 후 에는 PDF 도 지원하도록 했다.
어느 시점에서 세 클래스에 유사한 코드가 많음을 알았다.
데이터 형식을 처리 하는 코드는 다르지만 흐름은 거의 동일 하다는 것을 알았다.
알고리즘의 구조(흐름)는 그대로 두고 코드 코드 중복을 제거 하는 방법은 무었일까?
해결책
알고리즘을 일련의 단계로 나누고,
이러한 단계를 메서드로 전환하고,
단일 템플릿 메서드 내부에 이러한 메서드에 대한 일련의 호출을 넣을 것
알고리즘을 단계로 분할하여 하위 클래스가 실제 메서드가 아닌 이러한 단계를 재정의할 수 있도록 한다.
추상 단계 와 선택 단계로 구분 한다.
- 추상 단계는 모든 하위 클래스가 구현 해야 한다.
- 선택 단계는 이미 기본 구현이 있지만 필요한 경우 무시 할 수 있다.
간단히 표현 하자면
각각의 클래스 고유의 메소드는 유지 하고
그 메소드 들의 호름을 일반화 (추상화) 하여 하나의 공통된 인터페이스를 구현 하라는 뜻이다.
어차피 데이터 마이닝이라는 관점에서 접근 하면 각각의 처리 방법은 다르더라도
데이터 마이닝이라는 flow 자체는 공통되게 표현할 수 있다.
class diagram
AbstractClass
알고리즘의 단계로 작동하는 각 메소드와 이 메소드를 특정 순서로 호출하는 템플릿 메서드 선언
각 메소드는 abstract 로 선언만 되어 있거나 기본 구현이 있을 수 있다.
ConcreateClass
모든 단계를 재 정의 할 수 있지만 템플릿 메서드 (흐름제어) 는 재정의 할 수 없다.
class 별 고유한 알고리즘은 이곳에서 재정의 한다.
적용
클라이언트가 알고리즘의 특정 단계만 확장하고 전체 알고리즘이나 해당 구조는 확장하지 않도록 할때
약간의 차이가 있지만 거의 동일한 알고리즘을 포함하는 여러 클래스가 있는 경우
자동 사냥 프로그램을 만든다고 가정해 보자
자동 사냥 프로그램은 다음과 같이 동작 한다.
1. 몹을 찾는다.
2. 주 skill 을 통해 attack 한다.
3. 적이 공격할 경우 defence 한다.
4. 특수 공격을 한다.
5. 적이 공격할 경우 특수 defence 한다.
6. 마무리 일격을 가한다.
이러한 내용을 전사와 도적에 적용해 보자
abstract class (template method)
public interface IUnit
{
void AutoHunt();
}
public abstract class AbstractUnit : IUnit
{
public void AutoHunt()
{
FindMob();
MainAttack();
MainDefence();
SpecialAttack();
SpecialDefence();
FinalAttack();
}
public void FindMob()
{
Console.WriteLine($"{GetType().Name} (이)가 주변에서 적절한 Level 의 몹을 찾는다.");
}
protected abstract void MainAttack();
protected abstract void SpecialAttack();
protected abstract void MainDefence();
protected abstract void SpecialDefence();
protected abstract void FinalAttack();
}
Concreate class
public class Warrior : AbstractUnit
{
protected override void FinalAttack()
{
Console.WriteLine("목을 대검으로 베기");
}
protected override void MainAttack()
{
Console.WriteLine("대검 휘두르기");
}
protected override void MainDefence()
{
Console.WriteLine("방패로 막기");
}
protected override void SpecialAttack()
{
Console.WriteLine("점프하여 대검으로 찍기");
}
protected override void SpecialDefence()
{
Console.WriteLine("방패로 밀치기");
}
}
public class Rogue : AbstractUnit
{
protected override void FinalAttack()
{
Console.WriteLine("심장에 단검 찌르기");
}
protected override void MainAttack()
{
Console.WriteLine("단검으로 찌르기");
}
protected override void MainDefence()
{
Console.WriteLine("회피");
}
protected override void SpecialAttack()
{
Console.WriteLine("독을 단검에 바르고 찌르기");
}
protected override void SpecialDefence()
{
Console.WriteLine("반격");
}
}
사용법
public void Run()
{
AutoHunt(new Warrior());
AutoHunt(new Rogue());
}
public void AutoHunt(IUnit unit)
{
unit.AutoHunt();
}
// output
Warrior (이)가 주변에서 적절한 Level 의 몹을 찾는다.
대검 휘두르기
방패로 막기
점프하여 대검으로 찍기
방패로 밀치기
목을 대검으로 배기
Rogue (이)가 주변에서 적절한 Level 의 몹을 찾는다.
단검으로 찌르기
회피
독을 단검에 바르고 찌르기
반격
심장에 단검 찌르기
관련영상