템플릿 메소드 패턴(Template Method Pattern)
이 포스팅은 템플릿 메소드(Template Method) 패턴에 대해서입니다. 간단하게 최종 코드를 먼저 보고 그 뒤 하나씩 구현해보겠습니다.
최종 코드
public class TemplateMain { public static void main(String[] args) { // 전사는 전투 준비를 위해서 // 체력을 단련하고, 검을 닦고, 갑옷을 입습니다. Warrior warrior = new Warrior(); warrior.readyToBattle(); // 궁사는 전투 준비를 위해서 // 체력을 단련하고, 활을 손질하고, 화살을 준비합니다. Archer archer = new Archer(); archer.readyToBattle(); // 마법사는 전투 준비를 위해서 // 체력을 단련하고, 지팡이를 준비하고, 로브를 입습니다. Wizard wizard = new Wizard(); wizard.readyToBattle(); } }
하지만 최종코드만 봐서는 딱히 뭐가 다르게 구현되었는지 모르겠네요. 다만 주석 내용에 readyToBattle 메소드를 실행하기 위한 정보가 있습니다. 비슷한것도 있고 다른것도 있네요. 일단 전사, 궁사, 마법사가 같이 사용할 수 있도록 공통의 추상 클래스(abstract class)를 만들어 봅시다. 일단 전부 인간이라는 가정이니 "Person" 정도가 좋겠네요.
공통 추상 클래스
public abstract class Person { // 전투를 준비합니다. void readyToBattle(){ startBodyTraining(); prepareWeapon(); prepareArmor(); if( isUsingPrepareOther() ){ prepareOther(); } } // 상속 받은 클래스에서 수정할 수 없도록 final 키워드를 붙였습니다. final void startBodyTraining(){ System.out.println("체력을 단련합니다."); } // 무기를 손질할때 사용합니다. abstract void prepareWeapon(); // 뭔가를 걸칠 때 사용합니다. abstract void prepareArmor(); // 만약 다른 무언가를 사용하려면 true로 바꿔야 합니다. // 이 메소드는 "후킹(Hooking)" 용도로 사용합니다. boolean isUsingPrepareOther(){ return false; } // 다른 무언가가 필요하면 사용합니다. // isUsingPrepareOther 값에 의해 오버라이드 해서 사용 합니다. void prepareOther(){}; }
간략하게 살펴보면 readyToBattle 메소드를 실행하면 나머지 메소드가 차례대로 실행되는 것을 볼 수 있습니다. 이 중 startBodyTraining 메소드는 전사, 궁사, 마법사가 전부 체력 단련을 하는 공통점이 있기 때문에 abtract 키워드가 붙지 않았습니다.
여기서 특이한 메소드가 있는데 바로 isUsingPrepareOther 메소드입니다. 이 메소드는 후킹(Hooking)이라고 하는 용도로 사용합니다. 지금은 사용하지 않지만 상속 받은 클래스에서 오버라이드 해서 추가적으로 알고리즘을 변경할 수 있죠.
후킹을 어떻게 쓰는지는 다음 절에서 확인해 봅시다. 이제부터 전사, 궁사, 마법사 클래스를 만듭니다.
훅 메소드(hook method)
abstract 키워드를 붙이면 상속 받은 클래스는 반드시 해당 메소드를 구현해야 하지만 abstract 키워드를 붙이지 않고 훅 메소드로 만들면 반드시 구현할 필요가 없습니다. 상속 받은 클래스에서는 선택적으로 오버라이드할 수 있다는 얘기가 됩니다.
직업별 클래스
전사
public class Warrior extends Person { @Override void prepareWeapon() { System.out.println("검을 닦습니다."); } @Override void prepareArmor() { System.out.println("갑옷을 입습니다."); } }
전사 클래스는 내용이 별 것 없습니다. 전투 준비를 위해서 prepareWeapon 메소드와 prepareArmor 메소드에 준비할 것을 입력했습니다.
궁사
public class Archer extends Person { @Override void prepareWeapon() { System.out.println("활을 손질합니다."); } @Override void prepareArmor() {} @Override boolean isUsingPrepareOther() { return true; } @Override void prepareOther() { System.out.println("화살을 준비합니다."); } }
궁사 클래스에서는 후킹(Hooking)이 필요합니다. 화살을 준비하려면 기존에 처리 하지 않았던 prepareOther 메소드를 활성화 시켜야 하죠. 따라서 isUsingPrepareOther 메소드를 오버라이드하고 값을 true로 바꿔줬습니다. 앞으로 궁사 클래스가 전투 준비를 하면 prepareOther 메소드도 실행됩니다. 다만 갑옷은 입지 않아서 prepareArmor 메소드에는 공백으로 놔뒀습니다.
마법사
public class Wizard extends Person { @Override void prepareWeapon() { System.out.println("지팡이를 준비합니다."); } @Override void prepareArmor() { System.out.println("로브를 입습니다."); } }
마법사 클래스는 전사 클래스와 동일하게 작성합니다. 전투 준비를 위해서 지팡이를 준비하고 로브를 입으면 되죠.
실행 결과
이제 실제로 최종 코드를 돌릴 수 있게 되었습니다. 최종 코드를 돌려보면 다음과 같은 결과물이 출력됩니다.
체력을 단련합니다. 검을 닦습니다. 갑옷을 입습니다. --- 체력을 단련합니다. 활을 손질합니다. 화살을 준비합니다. --- 체력을 단련합니다. 지팡이를 준비합니다. 로브를 입습니다.
포스팅의 최종 코드에는 구분하는 선을 넣지 않았지만 제가 로컬에서 돌릴때 구분을 용이하게 하기 위해서 다음의 소스를 각 클래스별 사이사이에 넣었습니다.
System.out.println("---");
결과물만 놓고 보면 처음에 최종 코드의 주석대로 잘 나오는 것 같네요.
왜 사용할까요?
템플릿 메소드 패턴은 "알고리즘의 뼈대"를 맞추는 것에 있습니다. 즉, 전체적인 레이아웃을 통일 시키지만 상속받은 클래스로 하여금 어느정도 유연성을 주도록하는 디자인 패턴입니다.
추상 메소드(abstrcat method)와 훅 메소드(hook method)를 적절히 사용해서 전체적인 알고리즘의 뼈대를 유지하되 유연하게 기능을 변경할 수 있도록 하고자 할 때 사용하면 유용하겠네요. :3