관리 메뉴

IT.FARMER

템플릿 메소드 (Template Method Pattern) 본문

소프트웨어공학/Design Pattern

템플릿 메소드 (Template Method Pattern)

아이티.파머 2022. 11. 30. 15:47
반응형

템플릿 메소드 (Template Method Pattern)

소프트웨어 공학에서 동작상의 알고리즘의 뼈대를 정의하는 디자인패턴이다. 알고리즘의 구조를 변경하지 않고 특정 단계들을 다시 정의 할 수 있게 해준다. - 위키

템플릿 메소드 패턴은 슈퍼클래스에서 처리의 흐름을 제어하며 서브 클래스에서 그 처리의 구체적인 기능을 구현하는 패턴이다. 여러개의 서브클래스에서 공통으로 사용하는 기능은 수퍼클레스에서 구현하고 다른 기능들은 서브클레스에서 구현한다.

  • 전체적인 알고리즘의 구조(작업 플로어가 동일함)는 같으나 세부적인 내용이달라 각 서브클래스마다 다르게 구현해야 할때 사용한다.

예시 (1) : 케릭터 생성 및 최초 듀토리얼 진행

게임케릭터 생성후 최초 튜토리얼 진행시 같은 패턴으로 진행되나 보상받는 아이템이 다른경우에 사용 할 수있다.

예제

public abstract class Person {

    public void tutorialPlay() {

        this.characterCreation();
        this.ready();
        this.firstChapterMoveTown();

        System.out.printf("최초 퀘스트 수행여부 %s \n", isFirstQuestHookMethod());
        if (isFirstQuestHookMethod()) {
            this.firstQuestAccept();
            this.initQuest();
        }

        this.questRewardItem();
    }

    // 케릭터를 생성합니다.
    abstract void characterCreation() ;

    // 케릭터가 준비됬다.
    final void ready () {
        System.out.println("OOO 케릭터가 생성 되었습니다. ");
    }

    // 상속하는 클레스에서 변경하지 못하도록 final 을 지정한다.
    // 게임 시작시 무조건 NPC를 만나 마을로 이동해야 한다.
    final void firstChapterMoveTown() {
        System.out.println("NPC를 만나 Start 마을 [시작 마을]로 함께 이동합니다.");
    }

    // 상속하는 클레스에서 변경하지 못하도록 final 을 지정한다.
    //  - 마을로 이동한후 퀘스트를 수락해야 한다,
    //  - 케릭터 직업별로 퀘스트를 수해하지 않아도 되는 직업이 있다.
    final void firstQuestAccept() {
        System.out.println("케릭터별 최최 퀘스트를 수행합니다. ");
    }

    // 캐릭터별 최초 퀘스트 수행
    abstract void initQuest() ;

    // 캐릭별 최초 수행퀘스트후 보상아이템
    abstract void questRewardItem();

    // 최초클레스 생성시 쿼스트를 수행해야 하는지에 대한 hooking method
    protected boolean isFirstQuestHookMethod() {
        // prepareOther 혹은 hook method 라고 불리우며 추상클레스에서 abstract 가 붙지 않음 함수를 말한다.

        return false;
    }
}

hook method

Abstract 가 붙어 있는 메소드는 상속받은 클레스에서 반드시 정의해주어야 한다. 하지만 Template Method pattern 안에 abstract 가 붙어 있지 않고 정의된 메소드가 있는데 이것을 우리는 hooking method 혹은 prepareOther method 라고 한다. 상속받은 클레스에서 선택적으로 재정의해서 사용 할수있다

final method

공통으로 수행해야 하는 알고리즘의 패턴에서도 변경되지 않고 공통으로 사용가능한부분을 정의한다. final 을 붙임으로 상속받은 하위클레스에서 재정의가 불가능하게 한다.

부모 (Person) 클레스를 를 상속받은 서브 클레스들

public class Sorcerer extends Person {
    @Override
    void characterCreation() {
        System.out.println("[사악한 여자 마법사]를 생성하였습니다.");
    }

    @Override
    void initQuest() {
        System.out.println("[사악한 여자 마법사] 클레스의 최초 퀘스트를 수행합니다.");
    }

    @Override
    void questRewardItem() {
        System.out.println("[사악한 여자 마법사] 클레스의 무기를 획득합니다.");
    }
}

public class Knight extends Person {
    @Override
    void characterCreation() {
        System.out.println("[기사] 클레스를 생성하였습니다.");
    }

    @Override
    void initQuest() {
        System.out.println("[기사] 클레스의 최초 퀘스트를 수행합니다.");
    }

    @Override
    void questRewardItem() {
        System.out.println("[기사] 클레스의 무기를 획득합니다.");
    }

    @Override
    protected boolean isFirstQuestHookMethod(){
        return true;
    }
}

public class Warrior extends Person {
    @Override
    void characterCreation() {
        System.out.println("[전사] 클레스를 생성합니다.");
    }

    @Override
    void initQuest() {
        System.out.println("[전사] 클레스의 최초 퀘스트를 수행합니다.");
    }

    @Override
    void questRewardItem() {
        System.out.println("[전사] 클레스의 무기를 획득합니다.");
    }
}

Template method Main Class

public class TemplateMethodPatternMain {

    public static void main(String[] args) throws Exception {
        System.out.println("------------------------[Knight]-------------------------");
        Knight knight = new Knight();
        knight.tutorialPlay();

        System.out.println("------------------------[Warrior]-------------------------");
        Warrior warrior = new Warrior();
        warrior.tutorialPlay();

        System.out.println("------------------------[Sorcerer]-------------------------");
        Sorcerer sorcerer = new Sorcerer();
        sorcerer.tutorialPlay();

        System.out.println("------------------------[Warlock]-------------------------");
        Warlock warlock = new Warlock();
        warlock.tutorialPlay();

    }
}

실행결과

------------------------[Knight]-------------------------
[기사] 클레스를 생성하였습니다.
OOO 케릭터가 생성 되었습니다. 
NPC를 만나 Start 마을 [시작 마을]로 함께 이동합니다.
최초 퀘스트 수행여부 true 
케릭터별 최최 퀘스트를 수행합니다. 
[기사] 클레스의 최초 퀘스트를 수행합니다.
[기사] 클레스의 무기를 획득합니다.
------------------------[Warrior]-------------------------
[전사] 클레스를 생성합니다.
OOO 케릭터가 생성 되었습니다. 
NPC를 만나 Start 마을 [시작 마을]로 함께 이동합니다.
최초 퀘스트 수행여부 false 
[전사] 클레스의 무기를 획득합니다.
------------------------[Sorcerer]-------------------------
[사악한 여자 마법사]를 생성하였습니다.
OOO 케릭터가 생성 되었습니다. 
NPC를 만나 Start 마을 [시작 마을]로 함께 이동합니다.
최초 퀘스트 수행여부 false 
[사악한 여자 마법사] 클레스의 무기를 획득합니다.
------------------------[Warlock]-------------------------
[사악한 남자 마법사]를 생성하였습니다.
OOO 케릭터가 생성 되었습니다. 
NPC를 만나 Start 마을 [시작 마을]로 함께 이동합니다.
최초 퀘스트 수행여부 false 
[사악한 남자 마법사] 클레스의 무기를 획득합니다.

Process finished with exit code 0

hookin mehtod에 의해 최초 퀘스트 수행여부가 재정의되어 변하는것을 확인 할 수있다.

전체적인 알고리즘(순서)는 동일하나 각 직업별로 특징이 다르기때문에 케릭터를 생성하는 부분이 분리되어 있고 최초 퀘스트를 수행한뒤에 받는 아이템들은 모두 제각각 다르게 구성되어 있다. 이때 hooking method , abstract method 들을 적절하게 활용하여 템플릿 메소드 패턴을 완성 시켰다.

UML로 보면 다음과 같다.

예제 (2) : 자동차 제조사별로 시동을걸로 운전하는방법

자동차를 운전하기위해 시동을 걸고 점검을 하고 운전을 한다. 이때 시동을거는방법은 동일하나 시스템을 점검하고 운전하는방식은 각 제조사별로 별도로 구현해야 할때 템플릿메서드패턴을 적용

public abstract class Car {

    public void run() {
        this.start();

        if (this.isSmartCar() ) {
            carInspection();
        }
        this.drive();
    }

    final protected void start(){
        System.out.println("시동을 겁니다.");
    }

    // 제조사별 점검
    abstract protected void carInspection();

    // 제조사 자동차별로 운전하는 방식이 다르므로 서브클레스에서 구현
    abstract protected void drive() ;

    // 스트카인지 확인하기위한 hooking method
    protected boolean isSmartCar () {
        return true;
    }
}

public class HyundaeCar extends Car {
    @Override
    protected void carInspection() {
        System.out.println("현대차 소프트웨어 점검");
    }

    @Override
    protected void drive() {

        System.out.println("현대차 드라이빙");

    }

    @Override
    protected boolean isSmartCar () {
        // 기본 스마트카로 인식
        return true;
    }
}

public class KIACar extends Car{
    @Override
    protected void carInspection() {
        System.out.println("기아자동차 소프트웨어 점검");
    }

    @Override
    protected void drive() {
        System.out.println("가아자동차 드라이빙");
    }

    @Override
    protected boolean isSmartCar () {
        // 기본 스마트카로 인식
        return true;
    }
}

public class Genessis extends HyundaeCar{
}

public class K9 extends KIACar {
}

public class Spark extends HyundaeCar{

    @Override
    protected boolean isSmartCar () {
        return false;
    }
}

제조사별 TemplateMethodPattern Main Class

public class TemplateMethodPattern {

    public static void main(String[] args) {
        System.out.println("------------------------[Genessis]-------------------------");
        Genessis genessis = new Genessis();
        genessis.run();

        System.out.println("------------------------[K9]-------------------------");
        K9 k9 = new K9();
        k9.run();

        System.out.println("------------------------[Spark]-------------------------");
        Spark spark = new Spark();
        spark.run();

    }
}

실행결과

------------------------[Genessis]-------------------------
시동을 겁니다.
현대차 소프트웨어 점검
현대차 드라이빙
------------------------[K9]-------------------------
시동을 겁니다.
기아자동차 소프트웨어 점검
가아자동차 드라이빙
------------------------[Spark]-------------------------
시동을 겁니다.
현대차 드라이빙

Spark 는 스마트카가 아니어서 소프트웨어 점검을 따로 하지않고 바로 현대차를 운전한다.

그림으로 보면 다음과 같다.

 

Template Method의 장단점

장점

  • 중복코드를 없애고 서브 클레스에서는 비지니스로직에 집중할수있다. (SRP 원칙)
  • 나중에 새로운 비지니스 로직이 추가되거나 수정되어도 기존 슈퍼클레스의 코드를 수정하지 않아도 된다. (OCP 원칙)

단점

  • 구현할 클레스파일을 상속받아 계속 만들어 주어야 한다.

템플릿메시드 패턴과 비슷한 역활을하며 단점을 제거할수있는 디자인패턴으로 전략패턴(Strategy pattern)이 있다.

반응형