[그린컴퓨터] Server/JAVA(객체 지향 프로그래밍)

템플릿 메서드 응용하기 { 예제 이해하기, 클래스 기능과 관계, 클래스 설계하기, 추상 클래스와 다형성 }

Ben의 프로그램 2023. 6. 1. 18:46
728x90

예제 이해하기 (템플릿 메서드를 활용한 게임 캐릭터)

  • 템플릿 메서드까지 배웠으니, 재미있는 예제를 하나 만들어봅시다.
  • 예제 시나리오 :

    player 가 있고, 이 Player 가 게임을 합니다.
    게임에서 Player 가 가지는 레벨에 따라 할 수 있는 세 가지 기능이 있습니다. 
    바로 run(), jump(), turn() 입니다. 

    초보자 레벨 : 천천히 달릴 수 있습니다.
    중급자 레벨 : 빠르게 달리고 점프할 수 있습니다.
    고급자 레벨 : 엄청 빠르게 달리고 점프하고 턴할 수 있습니다. 

    모든 레벨에서 Player 가 사용할 수 있는 필살기인 go(int count) 메서드를 제공합니다. go( ) 메서드는 한 번 run하고, 매개변수로 전달된 count 만큼 jump 하고, 한 번 turn 합니다. 그 레벨에서 불가능한 기능을 요청하면 할 수 없다는 메시지를 출력합니다. 

클래스 기능과 관계 (좋은 습관)

  • 위의 예제 시나리오를 코드로 구현하기 전에 잠깐 생각해볼 것이 있습니다. 
  • 시나리오를 받자마자 바로 클래스로 만들어 코딩하는 것 보다 주어진 문제를 어떻게 해결할 것인지를 천천히 생각해 보고 손으로 클래스 다이어그램을 간략하게 그려 보는 것이 객체 지향 방식으로 문제를 해결하는 좋은 습관입니다. 
  • 큰 프로젝트를 진행할 때는 이 과정을 분석&설계 과정이라고 합니다. 
  • 시나리오에서 제시한 내용에 기반해 클래스를 생각해 봅시다. 
  • 우리의 예제를 한 번 생각해 볼까요?
  • 생각을 안 하고 간단하게 해결하는 방법은 Player 클래스를 만들고 현재 player의 레벨에 따라 if 조건문으로 코드를 구현하면 됩니다. 
  • 그런데, 이렇게 구현하면 level 의 가지 수가 만약 더 증가하여 7개가 된다면 if-else if-else 문을 7개나 만들어야 합니다. 
  • 이만큼 시나리오를 분석하고 클래스를 체계적으로 설계하는 과정은 코딩을 구현하는 것보다 더 중요합니다. 

클래스 설계하기

  • 좋은 습관을 기르는 연습을 해봅시다.
  • 시나리오를 어떻게 구현할지 분석&설계 과정을 가져봅시다
  • 우선 Player 클래스가 있어야 할 겁니다.
  • 클래스 다이어그램을 그려봅시다.

화살표 출발점이 다이아몬드 형태로 비어있는 것은 포함 관계를 나타냅니다. 상속 관계를 나타내는 화살표도 속이 비어있는 것을 볼 수 있습니다.

  • 그리고 PlayerLevel 클래스가 추상 클래스로 존재하고 각 레벨마다 공통 기능과 개별 기능이 있으므로 PlayerLevel 클래스를 상속 관계로 표현하겠습니다.
  • Player 클래스와 PlayerLevel 클래스는 포함(HAS-A) 관계입니다. 
  • 모든 Player 는 Level 을 갖습니다. 그런데, Player 가 일반적인 개념이고 Level 이 구체적인 개념은 아니기 때문에, Player 가 PlayerLevel 클래스를 포함하는 HAS-A 관계를 갖게 됩니다.
  • 레벨이 올라갈 수록 Player 가 할 수 있는 일이 달라집니다. 
  • 따라서 PlayerLevel 클래스는 추상 클래스로 만들어 모든 레벨에서 공통으로 수행하는 기능과 각 레벨마다 달라지는 기능을 구별하여 구현합니다. 
  • 그리고 PlayerLevel 추상 클래스를 상속받는 Beginner, Advanced, Super 클래스는 추상 메서드를 각자 필요에 따라 재정의하면 됩니다. 

Player 클래스

package gamelevel;

public class Player {
	private PlayerLevel level;  // Player 가 가지는 level 변수 선언
	
	public Player() {
		level = new BeginnerLevel();  // 묵시적 형 변환이 일어난다. 
		level.showLevelMessage();  // 가상 메서드에 의해 인스턴스의 메서드가 실행.
	}
	
	public PlayerLevel getLevel() {  // level 의 getter
		return level;
	}
	
	public void upgradeLevel(PlayerLevel level) { // 구체적인 모든 Level 들을 받을 수 있도록 상위 클래스인 PlayerLevel 형으로 매개변수를 설정함
		this.level = level;
		level.showLevelMessage();
	}
	
	public void play(int count) {
		level.go(count); // PlayerLevel 의 템플릿 메서드인 go( ) 호출
	}
}
  • Player 는 한 번에 하나의 상태이므로 PlayerLevel 자료형을 갖는 level 변수를 만들었습니다.
  • Player 는 생성될 때 최초로 Beginner Level 을 갖도록 설정함. 또한 해당 Level 의 정보를 보여주는 showLevelMessage 메서드를 호출했습니다. 
  • Player 의 Level 이 변할 때 변경할 level 을 매개변수로 받아서 Player 의 Level 을 바꾸어 주는 upgradeLevel 메서드를 만들었습니다. 
  • play 메서드는 int count 를 매개변수로 받아서 PlayerLevel 클래스의 템플릿 메서드인 go( ) 메서드를 count 매개변수와 함께 실행합니다.

PlayerLevel 클래스

public abstract class PlayerLevel {
	public abstract void run();
	public abstract void jump();
	public abstract void turn();
	abstract public void showLevelMessage();
	
	final public void go(int count) {
		run();
		for (int i = 0; i < count; i++) {
			jump();
		}
		turn();
	}
}
  • 각 레벨에서 수행할 공통 기능은 PlayerLevel 추상 클래스에서 선언합니다. 
  • go( ) 메서드는 시나리오대로 수행되어야 하므로 코드 내용을 완전히 구현했습니다.
  • go( ) 플랫폼 메서드는 재정의 되면 안되므로 final 예약어와 함께 선언합니다. 
  • 각 레벨마다 run( ), jump( ), turn( ), showLevelMessage( ) 메서드는 조금씩 다르게 구현되기 때문에 추상 메서드로 선언합니다. 

BeginnerLevel 클래스

public class BeginnerLevel extends PlayerLevel {

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

	@Override
	public void run() {
		System.out.println("천천히 달립니다.");
	}

	@Override
	public void jump() {
		System.out.println("Jump를 할 수 없습니다.");
	}

	@Override
	public void turn() {
		System.out.println("Turn를 할 수 없습니다.");
	}

}
  • 초보자 레벨에서는 천천히 달릴 수만 있습니다. 점프나 턴은 할 수 없도록 만듭니다.

AdvancedLevel 클래스

public class AdvancedLevel extends PlayerLevel{

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

	@Override
	public void run() {
		System.out.println("빨리 달립니다.");
	}

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

	@Override
	public void turn() {
		System.out.println("Turn를 할 수 없습니다.");
	}

}
  • 중급자 레벨에서는 빠르게 달리 수 있고 높이 점프할 수 있습니다. 턴은 할 수 없습니다.

SuperLevel 클래스

public class SuperLevel extends PlayerLevel{

	@Override
	public void showLevelMessage() {
		System.out.println("=== 상급자 레벨입니다 ===");
	}

	@Override
	public void run() {
		System.out.println("매우 빨리 달립니다.");
	}

	@Override
	public void jump() {
		System.out.println("매우 높이 Jump 합니다.");
	}

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

}
  • 고급자 레벨에서는 아주 빠르게 달리고 높이 뜁니다. 그리고 턴도 할 수 있습니다.

테스트 프로그램 만들어서 실행

public class MainBoard {

	public static void main(String[] args) {
		Player player = new Player();
		player.play(1);
		System.out.println(player.getLevel());
		
		PlayerLevel abvancedLevel = new AdvancedLevel();
		player.upgradeLevel(abvancedLevel);
		player.play(2);
		
		PlayerLevel superLevel = new SuperLevel();
		player.upgradeLevel(superLevel);
		player.play(3);
	}

}

  • Level 을 변경해줄 때 변경해주고자 하는 Level 의 인스턴스를 먼저 만들고 매개변수로 넣어주어야 합니다.

추상 클래스와 다형성

  • 앞에서 만든 예제는 추상 클래스를 활용한 다형성이 확보되는 명확한 예제였습니다. 
  • 모든 Level 클래스는 PlayerLevel 이라는 추상 클래스를 상속받았습니다. 
  • 그리고 Player 가 가질 수 있는 여러 레벨을 별도의 자료형으로 선언하지 않고 하나의 PlayerLevel 자료형 level 로 선언했습니다. 
  • 레벨을 변경하는 upgradeLevel( ) 메서드의 매개변수 자료형도 PlayerLevel 로 하나입니다. 
  • 따라서 레벨 클래스가 여러 개 존재하더라도 또는 새로운 레벨 클래스가 생겨나더라도 모든 레벨 클래스는 PlayerLevel 클래스로 대입될 수 있습니다. 
  • 모든 추상 클래스에 템플릿 메서드를 사용하는 것은 아니지만, 추상 클래스를 활용할 수 있는 좋은 패턴입니다.

정리하자면

  • 상위 클래스인 추상 클래스는 하위에 구현된 여러 클래스를 하나의 자료형으로 선언하거나 대입할 수 있습니다.
  • 추상 클래스에 선언된 메서드를 호출하면 가상 메서드에 의해 각 클래스에 구현된 기능이 호출됩니다. 
  • 즉 하나의 코드가 다양한 자료형을 대상으로 동작하는 다형성을 활용할 수 있습니다.