JAVA

템플릿 메서드 응용하기

seungmin576 2024. 12. 24. 20:51

게임을 한다고 가정을 한다.

게임 캐릭터에는 레벨이 있고 레벨은 경험치가 쌓이거나 어떤 이벤트를 통해 올라간다.

플레이어 레벨이 다르면 그 레벨마다 할 수 있는 역할도 다르다.

이와 같은 상황을 템플릿 메서드를 사용해 구현한다.


예제 시나리오

Player가 있고, 이 Player가 게임을 한다.

게임에서 Player가 가지는 레벨에 따라 할 수 있는 세 가지 기능이 있다.

바로 run(), jump(), turn()이다.

  • 초보자 레벨 : 천천히 달릴(run) 수 있다.
  • 중급자 레벨 : 빠르게 달리고(run) 점프할(jump) 수 있다.
  • 고급자 레벨 : 엄청 빠르게 달리고(run) 점프하고(jump) 턴할(turn) 수 있다.

모든 레벨에서 Player가 사용할 수 있는 필살기인 go(int count) 메서드를 제공한다.

go() 메서드는 한 번 run하고, 매개변수로 전달된 count만큼 jump하고, 한 번 turn 한다.

그 레벨에서 불가능한 기능을 요청하면 할 수 없다는 메시지를 출력한다.



클래스 기능과 관계

주어진 문제를 어떻게 해결할 것인지를 천천히 생각해 보고 손으로 클래스 다이어그램을 간략하게 그려 보는 것이 객체 지향 방식으로 문제를 해결하는 좋은 습관이다.

큰 프로젝트를 진행할 때는 이 과정을 분석 설계 과정이라고 한다.

시나리오에서 제시한 내용에 기반해 클래스를 생각해 본다.

 

간단하게 생각하면 Player 클래스를 만들고 현재 player의 레벨에 따라 if 조건문으로 코드를 구현하면 된다.

if(level == beginner)
//초보자 구현

if(level == advanced)
// 중급자 구현

if(level == super)
//고급자 구현

그런데 이렇게 구현하면 level 수만큼 if문이 증가해서 복잡해진다.

레벨마다 if-else if 문을 사용하여 코딩해야 하기 때문이다.


클래스 설계하기

각 플레이어가 가질 수 있는 레벨을 클래스로 분리한다.

각 레벨마다 공통 기능과 개별 기능이 있으므로 레벨 클래스를 상속 관계로 표현한다.

Player 클래스와 PlayerLevel 클래스는 포함(HAS -A) 관계이다.

게임에서 모든 Player는 자신의 레벨이 있기 때문에, Player 클래스에서 PlayerLevel을 멤버 변수로 갖는 것이다.

레벨이 올라갈수록 수행할 수 있는 기능이 달라진다.

그러므로 PlayerLevel 클래스를 추상 클래스로 만들어 모든 레벨에서 공통으로 수행하는 기능을 구현하고, 각 레벨마다 달라지는 기능은 추상 메서드로 만들어 둔다.

그리고 PlayerLevel을 상속받은 하위 클래스 BeginnerLevel, AdvancedLevel, SuperLevel에서 추상 메서드로 선언한 부분을 나름의 필요에 맞게 구체적으로 구현하면 된다.

 

Player 클래스

Player는 한 번에 하나의 레벨 상태이므로 level 변수에 레벨에 해당하는 인스턴스를 대입한다.

레벨을 변경할 수 있는 upgradeLevel() 메서드도 만든다.

package gamelevel;

public class Player {
    private PlayerLevel level;  //Player가 가지는 level 변수 선언

    public Player(){
        level = new BeginnerLevel();
        level.showLevelMessage();
    }

    public PlayerLevel getLevel(){
        return level;
    }

    public void upgradeLevel(PlayerLevel level){    //매개변수 자료형은 모든 레벨로 변환 가능한 PlayerLevel
        this.level = level; 
        level.showLevelMessage();   //레벨 변경 메서드. 현재 자신의 level을 매개변수로 받은 level로 변경하고 레벨 메시지 출력
    }

    public void play(int count){
        level.go(count);    //PlayerLevel의 템플릿 메서드 go() 호출
    }
}

Player 디폴트 생성자에서 초기 레벨을 Beginner로 지정하고 현재 레벨이 무엇인지 출력한다.

upgradeLevel() 메서드를 본다.

실제 게임에서는 특정 조건을 만족해야 레벨이 올라가겠지만 이 예제에서는 매개변수로 넘어온 레벨로 바로 업그레이드한다.

이 메서드에는 모든 레벨이 매개변수로 대입될 수 있기 때문에, 모든 레벨의 상위 클래스인 PlayerLevel을 매개변수의 자료형으로 정했다.

play() 메서드에서는 PlayerLevel 클래스가 제공하는 go() 메서드를 호출한다.

 

PlayerLevel 클래스

각 레벨에서 수행할 공통 기능은 PlayerLevel 추상 클래스에서 선언한다

package gamelevel;

public abstract class PlayerLevel {
    public abstract void run();
    public abstract void jump();
    public abstract void turn();
    public abstract void showLevelMessage();

    final public void go(int count){    //재정의되면 안 되므로 final로 선언
        run();
        for (int i = 0; i < count; i++) {
            jump();
        }
        turn();
    }
}

각 레벨마다 run(), jump(), turn(), showLevelMessage() 메서드는 조금씩 다르게 구현되기 때문에 추상 메서드로 선언한다.

go() 메서드는 시나리오대로 수행되어야 하므로 코드 내용을 완전히 구현한다.

Player가 go()를 호출하면 run(), jump(), turn() 메서드가 순서대로 호출될 것이다.

이 코드는 모든 레벨에서 동일하고 변하면 안 되는 것이므로 final 예약어를 사용해 템플릿 메서드로 구현한다.

각 레벨에서는 해당 레벨별로 제공하는 run(), jump(), turn(), showLevelMessage() 기능을 구현한다.

 

초보자 레벨 클래스

초보자 레벨에서는 천천히 달릴 수만 있고 점프나 턴은 할 수 없다

package gamelevel;

public class BeginnerLevel extends PlayerLevel{
    @Override
    public void run() {
        System.out.println("천천히 달린다.");
    }

    @Override
    public void jump() {
        System.out.println("Jump할 줄 몰라 임마");
    }

    @Override
    public void turn() {
        System.out.println("Turn도 못해 임마");
    }

    @Override
    public void showLevelMessage() {
        System.out.println("**** 초보자 레벨이다. *****");
    }
}

 

중급자 레벨 클래스

중급자 레벨에서는 빠르게 달릴 수 있고 높이 점프할 수 있다.

턴은 못 쓴다.

package gamelevel;

public class AdvancedLevel extends PlayerLevel {
    @Override
    public void run() {
        System.out.println("빨리 달린다.");
    }

    @Override
    public void jump() {
        System.out.println("높이 Jump 한다.");
    }

    @Override
    public void turn() {
        System.out.println("Turn은 못해");
    }

    @Override
    public void showLevelMessage() {
        System.out.println("****** 중급자 레벨이다. **********");
    }
}

 

 

고급자 레벨 클래스

고급자 레벨에서는 엄청 빠르게 달릴 수 있고 아주 높게 점프할 수 있다.

그리고 한 바퀴 턴하는 기술까지 사용할 수 있도록 클래스를 구현한다.

package gamelevel;

public class SuperLevel extends PlayerLevel{
    @Override
    public void run() {
        System.out.println("엄청 빨리 달린다.");
    }

    @Override
    public void jump() {
        System.out.println("아주 높이 jump 한다");
    }

    @Override
    public void turn() {
        System.out.println("한 바퀴 돈다");
    }

    @Override
    public void showLevelMessage() {
        System.out.println("***** 고급자 레벨이다. *****");
    }
}

 

테스트 프로그램 작성해서 실행하기

다이어그램에서 제시한 클래스를 모두 구현하였으므로 이제 테스트 프로그램을 만들어 실행

package gamelevel;

public class MainBoard {
    public static void main(String[] args){
        Player player = new Player();
        player.play(1);

        AdvancedLevel aLevel = new AdvancedLevel();
        player.upgradeLevel(aLevel);
        player.play(2);

        SuperLevel sLevel = new SuperLevel();
        player.upgradeLevel(sLevel);
        player.play(3);
    }
}

Player 클래스의 디폴트 생성자는 초보자 레벨로 시작하도록 구현되어 있다.

Player 클래스를 생성하고 인스턴스를 player 참조 변수에 대입한다.

play() 메서드의 매개변수에 1을 입력하면 초보자 레벨이기 때문에 천천히 달린 후 점프할 줄 모른다는 문구와 턴할 줄 모른다는 문구를 출력한다.

중급자 레벨 클래스를 새로 생성하고 aLevel 참조 변수를 upgradeLevel() 메서드에 대입해서 레벨을 업그레이드한다.

이제 player는 중급자 레벨이다.

play() 메서드의 매개변수에 값을 대입하면 빨리 달린 후 매개변수로 입력된 값만큼 점프한다.

중급자 레벨에서도 아직 턴할 수는 없다.

고급자 레벨도 비슷한 방식으로 수행된다.


추상 클래스와 다형성

앞에서 만든 Player 클래스와 PlayerLevel 클래스에서 다형성이 구현된 코드를 보았다.

모든 레벨 클래스는 PlayerLevel 클래스를 상속받았다.

그리고 Player가 가질 수 있는 여러 레벨을 별도의 자료형으로 선언하지 않고 PlayerLevel로 선언한다.

레벨을 변경하는 upgradeLevel() 메서드의 매개변수 자료형도 PlayerLevel이다.

따라서 레벨 클래스가 여러 개 존재하더라도 모든 클래스는 PlayerLevel 클래스로 대입될 수 있다.

level.go() 메서드가 호출되면 가상 메서드에 의해 각 레벨 클래스에 구현된 레벨별 기능이 호출된다.

정리하면 상위 클래스인 추상 클래스는 하위에 구현된 여러 클래스를 하나의 자료형(상위 클래스 자료형)으로 선언하거나 대입할 수 있다.

추상 클래스에 선언된 메서드를 호출하면 가상 메서드에 의해 각 클래스에 구현된 기능이 호출된다.

즉 하나의 코드가 다양한 자료형을 대상으로 동작하는 다형성을 활용할 수 있는 것이다.

 

모든 추상 클래스에 템플릿 메서드를 사용하는 것은 아니지만, 추상 클래스를 활용할 수 있는 좋은 패턴이다.

'JAVA' 카테고리의 다른 글

인터페이스  (0) 2024.12.26
final  (2) 2024.12.26
템플릿 메서드 (디자인패턴)  (0) 2024.12.24
추상 클래스  (0) 2024.12.24
다운 캐스팅, instanceof  (1) 2024.12.23