JAVA Basic) 템플릿 메서드(template method)
Programming/Java 기초

JAVA Basic) 템플릿 메서드(template method)

728x90

 

목차


     

    <추상 클래스와 템플릿 메서드>

    '템플릿(template)'이란 용어는 틀, 견본을 뜻하는 용어다. 즉 틀이 있는 메서드라는 의미.
    싱글톤 패턴과 같은 일종의 디자인 패턴.

    템플릿 메서드는 추상 클래스를 사용하여 구현할 수 있다.

    package template;
    
    public abstract class Car {
    	public abstract void drive();
    	public abstract void stop();
    
    	public void startCar() {
    		System.out.println("시동을 켭니다.");
    	}
    	
    	public void turnOff() {
    		System.out.println("시동을 끕니다.");
    	}
    	
    	final public void run() {
    		startCar();
    		drive();
    		stop();
    		turnOff();
    	}
    }

    Car클래스는 drive()와 stop()추상 메서드와 3개의 구현된 메서드를 가지고 있다.
    자동차가 시동을 켜고(turnOn()) 끄는(turnOff()) 방법은 어느 차나 동일하다. 그래서 미리 코드는 구현해 둔것.
    추상 메서드는 차종에 따라 다른 방식으로 움직일 수 있어 추상 메서드로 선언되었다.
    run()메서드는 '템플릿 메서드'로 자동차가 달리는 방법을 순서대로 구현해두었다. 시동을 켜고, 달리고, 브레이크로 멈춘 후 시동을 끈다. 만약 Car클래스를 상속받으면 어떤 자동차든 모두 이 순서대로 동일한 방식으로 달리는 것 

    Car클래스를 상속받을 클래스 2개를 구현하자.
    자율주행차(AICar)와 일반차(ManualCar) 두 개가 Car를 상속받는다.
    이 클래스들은 Car를 상속받기 때문에 구현되지 않은 추상 메서드를 구현해야한다. 
    자동차 종류에 따라 구현 내용이 달라지는 부분추상 메서드로 만들고 공통으로 사용하는 메서드는 추상 클래스에 구현하여 상속 받아 사용한다.

     

     

    AICar클래스

    package template;
    
    public class AICar extends Car {
    	@Override
    	public void drive() {
    		System.out.println("자율 주행합니다.");
    		System.out.println("자동차가 알아서 방향을 전환합니다.");
    	}
    	
    	@Override
    	public void stop() {
    		System.out.println("자동차가 스스로 멈춥니다.");
    	}
    }

    AICar클래스는 Car클래스를 상속받았고, drive(), stop() 추상 메서드를 구현했다.
    AICar는 자율 주행을 하고 방향도 알아서 전환한다. 사람은 시동을 켜고 끄기만 하면 된다.

    ManualCar 클래스

    package template;
    
    public class ManualCar extends Car {
    	@Override
    	public void drive() {
    		System.out.println("사람이 운전합니다.");
    		System.out.println("사람이 핸들을 조작합니다.");
    	}
    
    	@Override
    	public void stop() {
    		System.out.println("브레이크를 밟아 정지합니다.");
    	}
    }

    Manual 클래스도 AICar 클래스와 마찬가지.

    CarTest클래스를 만들어 어떻게 움직이는지 확인하자.

    package template;
    
    public class CarTest {
    	public static void main(String[] args) {
    		System.out.println("===자율 주행 자동차===");
    		Car myCar = new AICar();
    		myCar.run();
    		
    		System.out.println("===일반 자동차====");
    		Car manCar = new ManualCar();
    		manCar.run();
    	}
    }

     

    템플릿 메서드의 역할

    CarTest에서 두 개의 인스턴스(myCar,manCar)를 생성, 그리고 run()을 호출했다.
    run()은 차가 어떻게 달려야 하는지를 Car클래스에 미리 구현된 메서드이다. 작동 순서는 어느 차나 동일하기 때문에 이렇게 템플릿 메서드를 정의해 실행 순서와 시나리오를 정해놓는 것.
    메서드 앞에 final예약어를 사용해 하위 클래스가 메서드를 재정의할 수 없도록 한다.


    <템플릿 메서드 응용>

    우리가 게임을 할 때를 가정해보자.
    게임 캐릭터에는 레벨이 있다. 플레이어 레벨이 다르면 그 레벨마다 할 수 있는 역할도 달라지게 된다.


    [예제 시나리오]

    Player가 있고, 이 Player가 게임을 한다.
    게임에서 Player의 레벨에 따라 할 수 있는 세가지 기능(run, jump, turn)이 있다.
    -초보자 레벨: 천천히 달릴 수 있다
    -중급자 레벨: 빠르게 달리고 점프할 수 있다.
    -고급자 레벨: 엄청 빠르게 달리고 점프하고 턴할 수 있다.


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


    클래스 기능과 관계

    예제 시나리오 코드로 구현하기 전에 주어진 문제를 어떻게 해결할 것인지 천천히 생각해 보고, 손으로 클래스 다이어그램을 간략하게 그려보는 것객체 지향 방식으로 문제를 해결하는 좋은 습관이다.
    큰 프로젝트를 진행할 때는 이 과정을 '분석·설계 과정'이라고 한다.

     간단하게 생각하면 Player 클래스를 만들고 현재 Player의 레벨에 따라 if조건문으로 코드를 구현하면 된다.
    의사 코드(pseudo code)로 작성하면 다음 코드와 같다.

    if(level == beginner)
    	//beginner 기능 구현
    else if(level == advanced)
    	//advanced 기능 구현
    else if(level == super)
    	//super 기능 구현

    그런데 이렇게 구현하면 level의 수 만큼 if문이 길어지며 복잡해진다.

    <클래스 설계·작성>

    프로그램 설계

    이제 클래스를 좀 더 체계적으로 설계해보자.
    각 플레이어가 가질 수 있는 레벨을 클래스로 분리.
    공통 기능과 개별 기능이 있으므로 레벨 클래스를 상속 관계로 표현.

    다음 클래스 다이어그램에 포함된 클래스는 모두 하나의 패키지에 만들어야 프로그램이 제대로 실행된다.

    클래스 다이어그램

    Player클래스와 PlayerLevel클래스는 포함(HAS-A)관계이다.
    게임에서 모든 Player는 자신의 레벨이 있기 때문에, Player클래스에서 PlayerLevel을 멤버 변수로 갖는 것.

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

    Player클래스

    Player는 한 번에 하나의 레벨 상태이므로 level변수에 레벨에 해당하는 인스턴스를 대입한다.
    레벨을 변경할 수 있는 upgradeLevel()메서드로 만든다.

    package gamelevel;
    
    public class Player {
    	private PlayerLevel level;	//Player가 가지는 PlayerLevel형 변수level 선언
    	
    	public Player() {
    		level = new BeginnerLevel();
    		level.showLevelMessage();
    	}	//디폴트 생성자, 처음 생성되면 BeginnerLevel로 시작하며 레벨 메시지 출력
    	
    	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디폴트 생성자에서 초기 레벨을 BeginnerLevel로 지정하고 현재 레벨이 무엇인지 출력.
    • upgradeLevel()메서드에서는 모든 레벨이 매개변수로 대입될 수 있기 때문에, 모든 레벨의 상위 클래스인 PlayerLevel을 매개변수의 자료형으로 된다.
    • play()메서드는 PlayerLevel클래스가 제공하는 go()메서드를 호출한다.
       

    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) {
    		run();
    		for(int i = 0; i < count; i++) {
    			jump();
    		}
    		turn();
    	}	// 재정의되면 안되므로 final선언.
    }
    • 각 레벨마다 run, jump, turn, showLevelMessage() 메서드는 조금씩 다르게 구현되기 때문에 추상메서드로 선언.
    • go()메서드는 시나리오대로 수행되어야 하므로 코드 내용을 완전히 구현했다. Player가 go()를 호출하면 run, jump, turn()메서드가 순서대로 호출. 이 코드는 모든 레벨에서 동일하고 변하면 안되기에 final예약어를 사용해 템플릿 메서드로 구현된다.
    • 각 레벨에서는 해당 레벨별로 제공하는 run, jump, turn, showLevelMessage() 기능을 구현하게 된다.

    BeginnerLevel클래스

    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("*****초보자 레벨입니다.*****");
    	}
    }
    • 초보자 레벨에서는 천천히 달릴 수만 있다.

    AdvancedLevel클래스

    package gamelevel;
    
    public class AdvencedLevel 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("*****중급자 레벨입니다.*****");
    	}
    }
    • 중급자 레벨에서는 빠르게 달릴 수 있고, 높이 점프할 수 있다.

    SuperLevel클래스

    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("한 바퀴 Turn합니다.");
    	}
    
    	@Override
    	public void showLevelMessage() {
    		System.out.println("*****고급자 레벨입니다.*****");
    	}
    }
    • 고급자 레벨에서는 엄청 빠르게 달릴 수 있고, 아주 높게 점프도 가능하며, 그리고 한 바퀴 턴하는 기술도 가능하도록 구현.

    테스트 프로그램 실행

    다이어그램에서 제시한 클래스를 모두 구현했다.
    이제 테스트 프로그램을 만들어 실행하도록 하자.

    package gamelevel;
    
    public class MainBoard {
    	public static void main(String[] args) {
    		Player player = new Player(); //처음 Player클래스를 생성하면 초급자레벨
    		player.play(1);
    		
    		AdvencedLevel aLevel = new AdvencedLevel();
    		player.upgradeLevel(aLevel);
    		player.play(2);
    		
    		SuperLevel sLevel = new SuperLevel();
    		player.upgradeLevel(sLevel);
    		player.play(3);
    	}
    }
    • Player클래스의 디폴트 생성자는 초보자 레벨로 시작하도록 구현되어있다. Player클래스가 처음 생성되면 레벨 메시지가 출력되고, Player인스턴스를 참조변수 player에 대입. play()메서드의 매개변수에 1을 입력하면 Player클래스의 play()메서드에 1을 입력받고 go()메서드가 실행된다. BeginnerLevel클래스의 run, jump, run()메서드들이 1번씩 출력하게 된다.
    • 중급자 레벨 클래스를 새로 생성하고 참조변수 aLevel을 upgradeLevel 메서드에 대입해 Player의 레벨은 중급자 레벨이 된다. 동시에 AdvencedLevel클래스의 showLevelMessage()가 실행. play()의 매개변수에 값을 대입하면 run()출력 후 대입된 값만큼 점프를 한다. 그리고 턴을 할 수 없다는 메시지가 출력.
    • 고급자 레벨도 중급자 레벨과 마찬가지로 실행된다.

    <추상 클래스의 다형성> 

    • 앞에서 만든 Player클래스와 PlayerLever 클래스에서 다형성이 구현된 코드를 보았다.
    • 모든 레벨 클래스(초, 중, 고급자 레벨)는 PlayerLever클래스를 상속받았다.
    • 그리고 Player가 가질 수 있는 여러 레벨(level)을 별도의 자료형으로 선언하지 않고, PlayerLevel로 선언했다. (PlayerLevel level 변수)
    • 레벨을 변경하는 upgradeLevel()메서드의 매개변수 자료형도 PlayerLevel이었다. 따라서 레벨 클래스가 여러 개 존재하더라도 PlayerLevel클래스로 대입될 수 있었다.
    • level.go() 메서드가 호출되면 가상 메서드에 의해 각 레벨 클래스에 구현된 레벨별 기능이 호출된다.

    정리하자면, 상위 클래스인 추상 클래스하위에 구현된 여러 클래스를 하나의 자료형(상위 클래스 자료형)으로 선언하거나 대입이 가능하다. (ex. upgradeLevel(PlayerLevel level))
    추상 클래스에 선언된 메서드를 호출하면 가상 메서드에 의해 각 클래스에 구현된 기능이 호출된다.
    하나의 코드가 다양한 자료형을 대상으로 동작하는 다형성을 활용할 수 있는 것이다.


    [Do it! 자바 프로그래밍 입문] 도서로 공부하며 정리한 글입니다.

    300x250