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 클래스로 대입될 수 있습니다.
- 모든 추상 클래스에 템플릿 메서드를 사용하는 것은 아니지만, 추상 클래스를 활용할 수 있는 좋은 패턴입니다.
정리하자면
- 상위 클래스인 추상 클래스는 하위에 구현된 여러 클래스를 하나의 자료형으로 선언하거나 대입할 수 있습니다.
- 추상 클래스에 선언된 메서드를 호출하면 가상 메서드에 의해 각 클래스에 구현된 기능이 호출됩니다.
- 즉 하나의 코드가 다양한 자료형을 대상으로 동작하는 다형성을 활용할 수 있습니다.
'[그린컴퓨터] Server > JAVA(객체 지향 프로그래밍)' 카테고리의 다른 글
| 인터페이스란 { 구현 코드가 없는 인터페이스, 클래스에서 인터페이스 구현하기, 인터페이스 구현과 형 변환 } (0) | 2023.06.02 |
|---|---|
| final 예약어 { 상수를 의미하는 final 변수, 상속할 수 없는 final 클래스, TDD } (0) | 2023.06.01 |
| 템플릿 메서드 { 추상 클래스와 템플릿 메서드, 템플릿 메서드의 역할, final } (0) | 2023.06.01 |
| 추상 클래스 { 추상 클래스란, 추상 클래스 구현하기, 추상 클래스를 만드는 이유 } (0) | 2023.05.31 |
| 다운 캐스팅과 instanceof { 다운 캐스팅이란, instanceof란 } (0) | 2023.05.31 |