Abstract Class and Template Method with Java

민준·2023년 3월 7일
0
post-thumbnail

1. Template Method

템플릿 메서드 는 추상 메서드나 구현된 메서드를 활용해서 전체 기능의 흐름(시나리오)을 정의하는 메서드입니다.
'싱글톤 패턴'처럼 디자인 패턴의 종류 중 하나입니다.
'final'은 상수를 선언할 때 사용했었는데, 메서드에 선언하면 하위 클래스에서 재정의 할 수 없습니다.
추상 클래스로 선언된 상위 클래스에 템플릿 메서드를 활용하여 전체적인 흐름을 정의하고
필요시 추상 메서드로 선언해서 하위 클래스에서 구현하도록 합니다.

final 예약어

  1. final 변수는 값이 변경 될 수 없는 상수입니다.
  2. final 변수는 오직 한 번만 값을 할당할 수 있습니다.(위와 같은 말입니다. 즉 초기화를 하면 그 값이 변하지 않습니다.)
    👉 하나의 클래스에 상수값을 선언하면 인스턴스를 생성할 필요 없이 여러 자바 파일에서 공유하기 편리합니다.
  3. final 메서드는 하위 클래스에서 재정의 할 수 없습니다.(Cannot override the final method from Car)
  4. final 클래스는 더 이상 상속되지 않습니다.

실습 1)

public class CarTest {

	public static void main(String[] args) {
		
        // 'Car' 타입의 'ManualCar' 인스턴스 (업 캐스팅)
		Car myCar = new ManualCar();
		myCar.run();
        
        // 'Car' 타입의 'AICar' 인스턴스 (업 캐스팅)		
		Car yourCar = new AICar();
		yourCar.run();
		
	}
}

아래 추상 클래스에서 추상 메서드를 선언했는데, 만약 후에 추가적으로 추상 메서드를 선언한다면
하위 클래스들에서 해당 메서드를 구현해야할 책임이 생기기 때문에 컴파일 되지 않습니다.

반면 'washCar()'와 같이 중괄호로 여닫아 비록 구현 코드는 없지만 정의가 된 경우,
필요에 의해 하위 클래스에서 재정의(오버라이딩)해서 사용할 수 있습니다. 이를 '훅 메서드'라고도 합니다.
추상 메서드와의 차이점은 구현에 대한 책임이 없다는 점입니다.

// 추상 클래스 'Car'
public abstract class Car {
	
    // 추상 메서드 선언
	public abstract void drive();
	public abstract void stop();
	
	public void washCar() {}
	
	public void startCar() {
		System.out.println("시동을 켭니다.");
	}
	
	public void turnOff() {
		System.out.println("시동을 끕니다.");
	}

	// 시나리오, 템플릿 메서드 'run()'
	public final void run() {
		startCar();
		drive();
		washCar();
		stop();
		turnOff();
	}
}

'run()' 메서드는 메서드들을 호출해 'Car' 인스턴스의 일련의 시나리오를 정의한 것입니다.
이 프로그램에서 자동차의 기능은 차종마다 차이는 있지만
시동을 켜고, 주행하고, 시동을 끄는 등의 일련의 행위들은 변하지 않을 것입니다.

그러므로 오버라이딩 등의 조작이 불가능하도록 final 예약어를 사용합니다.
이 경우 하위 클래스에서 오버라이딩을 시도할 경우 오류가 나며 해당 오류 메시지는 아래와 같습니다
'Cannot override the final method from Car'

이를 '템플릿 메서드'라 합니다.

// 하위 클래스 1. 'manualCar'
public class ManualCar extends Car{

	@Override
	public void drive() {
		System.out.println("사람이 운전합니다.");
		System.out.println("사람이 핸들을 조작합니다.");
		
	}

	@Override
	public void stop() {
		System.out.println("사람이 브레이크로 정지합니다.");
		
	}
}

// 하위 클래스 2. 'AICar'
public class AICar extends Car{

	@Override
	public void drive() {
		System.out.println("자율 주행합니다.");
		System.out.println("자동차가 스스로 방향을 전환합니다.");
	}

	@Override
	public void stop() {
		System.out.println("자동차가 스스로 멈춥니다.");
	}
}

실습 2)

// 프로그램 실행부
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);
		
	}
}

'go()' 메서드가 호출되면 추상 메서드로 선언한 'run()', 'jump()', 'turn()'이 동작합니다.
이 부분이 '템플릿 메서드'입니다. 모든 클래스가 실행할 일련의 시나리오를 정의합니다.
대신 클래스마다 같은 이름의 메서드를 구현하지만 내용은 상이할 수 있습니다.
이 부분에서는 '오버라이딩'과 같은 개념으로 볼 수 있습니다.

다만 차이점은 일반적인 오버라이딩의 경우
이미 상위 클래스에서 메서드를 구현한 상태이고 하위 클래스에서 재정의하는 것이 의무가 아니지만
추상 클래스에서 추상 메서드를 선언만 하고 하위 클래스에서 구현하는 것에는 책임이 생깁니다.

// 추상 클래스
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();
	}
}

추상 클래스를 상속받은 하위 클래스는, 추상 클래스에서 선언한 추상 메서드를 구현할 책임이 있습니다.

// 하위 클래스 1. 'BeginnerLevel'
public class BeginnerLevel extends PlayerLevel {

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

	@Override
	public void jump() {
		System.out.println("해당 기능을 쓸 수 없습니다.");
	}

	@Override
	public void turn() {
		System.out.println("해당 기능을 쓸 수 없습니다.");
	}

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

// 하위 클래스 2. 'AdvancedLevel'
public class AdvancedLevel extends PlayerLevel{

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

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

	@Override
	public void turn() {
		System.out.println("해당 기능을 쓸 수 없습니다.");
	}

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

// 하위 클래스 3. 'SuperLevel'
public class SuperLevel extends PlayerLevel{

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

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

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

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

	private PlayerLevel level;
	
	// Default Constructor, 기본 생성자를 정의합니다.
    // 인스턴스를 생성하면 동시에 'PlayerLevel level' 인스턴스가 생성됩니다.
	public Player() {
		level = new BeginnerLevel();
		level.showLevelMessage();
	}

	// 레벨을 반환하는 메서드
	public PlayerLevel getLevel() {
		return level;
	}

	// 레벨 인스턴스를 받아 플레이어 인스턴스의 레벨을 올려주는 메서드
	public void upgradeLevel(PlayerLevel level) {
		this.level = level;
		level.showLevelMessage();
	}
	
    // 플레이어의 레벨에 따라 상이한 시나리오를 실행하는 메서드
	public void play(int count) {
		level.go(count);
	}
}
profile
백엔드 포지션 공부 중입니다.

0개의 댓글