Programming Language/자바(JAVA)

Do it! 자바 프로그래밍 입문 / 9장 추상 클래스

토자맨 2023. 1. 4. 03:58

9.1 추상 클래스란?(+ 추상 클래스를 만드는 이유)

상속받는 클래스를 위해 만드는 클래스이다.
즉, 포괄 개념의 상속 클래스에게 각자 다른 기능을 하는 공통의 추상 메소드를 상속해주기 위해 만드는 클래스이다.
추상 메소드 display()가 있다고 가정할 때 DeskTop과 NoteBook이 display() 함수를 상속받고 각자 다른 기능으로 공통의 display() 함수를 사용하는 것이다.

  1. 추상 클래스는 항상 추상 메소드를 포함한다.
  2. 추상 메소드의 구현 코드가 없으므로 추상 클래스의 인스턴스를 생성할 수 없다.
  3. 형 변환은 할 수 있다.

추상 메소드란?

  1. 상속받는 클래스들마다 각자 구현 코드를 작성해서 각자 다른 기능으로 사용
  2. 구현 코드와 함수의 몸체('{}')가 없고 ';'를 이용해서 선언한다.
  3. 상속받는 클래스에서 추상 메소드의 구현 코드를 작성하기 때문에 같은 메소드지만 내용은 각자 다르다.
    즉, 상속받은 클래스마다 같은 함수를 각자 다른 기능으로 사용하려고 만드는 것이 추상 메소드다.
public abstract class Computer // 추상 클래스
{
    public abstract int display(); // 추상 메소드
    public abstract int typing(); // 추상 메소드
}

위 추상 클래스를 상속받는 클래스는 두 가지로 나뉜다.

  1. 추상 메소드를 구현하지 않은 추상 클래스(일부 구현은 가능)
  2. 추상 메소드를 모두 구현한 일반 클래스
// 1. 추상 메소드를 구현하지 않은 추상 클래스(일부 구현은 가능)
public abstract class NoteBook extends Computer
{
    public void display() {
        System.out.println("노트북"); // 추상 메소드를 NoteBook만의 기능으로 구현
    }
}

2. 추상 메소드를 모두 구현한 일반 클래스
public class DeskTop extends Computer
{
    public void display() {
        System.out.println("데스크톱"); // 추상 메소드를 DeskTop만의 기능으로 구현
    }
    public void typing() {
        구현코드
    }
}

9.2 템플릿 메소드

템플릿 메소드의 역할

템플릿 메소드 : 메소드 실행 순서와 시나리오를 미리 정해놓은 메소드
즉, 로직의 흐름이 정해놓은 메소드이기 때문에 상속받는 클래스에서 재정의 할 수 없게 final 예약어를 사용

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();
    }
}

자율주행차와 일반차의 달리는 시나리오는 같고 그게 run() 템플릿 메소드이다.

9.3 템플릿 메소드 응용

플레이어가 있고 플레이어 레벨이 3가지로 나뉜다 가정하자

레벨 메소드
초급자 레벨 느린 run()
중급자 레벨 빠른 run(), jump()
고급자 레벨 매우 빠른 run(), junp(), turn()

이때 8장에서 말한 것처럼 상속을 사용하지 않고 if-else 조건문을 사용해서 메소드마다 구분해주는 방법이 있지만 나눠야 할 가지수(레벨)이 많아 질수록 복잡하고 코드 가독성이 좋지 않기 때문에 상속 개념을 이용해서 코드를 작성하는 것이 현명하다.

Player과 PlayerLevel은 포함(HAS-A) 관계이다.

public class Player {

    private PlayerLevel level; // level 변수의 자료형을 PlayerLevel 클래스로 설정한다.

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

    public PlayerLevel getLevel() {
        return level;
    }

    public void upgradeLevel(PlayerLevel level) {
        this.level = level; // level 변수가 형 변환 돼서 저장된다.
        level.showLevelMessage();
    }

    public void play(int count){ // play 함수를 호출하면 PlayerLevel 클래스의 go 함수를 호출한다.
        level.go(count);
    }
}
public abstract class PlayerLevel {

    public abstract void run();
    public abstract void jump();
    public abstract void turn();
    public abstract void showLevelMessage();

    // level 변수에 맞는 클래스의 오버라이딩(재정의)된 메소드를 호출한다.
    // 즉, Player의 level에 따라 재정의된 메소드가 호출된다.
    final public void go(int count) 
    {
        run();
        for(int i=0; i<count; i++){
            jump();
        }
        turn();
    }
}
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("***** 초보자 레벨 입니다. *****");
    }

}
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("***** 중급자 레벨 입니다. *****");
    }
}
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("***** 고급자 레벨 입니다. *****");
    }

}
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);

    }
}

결국 복잡하게 if-else 조건문을 사용하지 않고 하나의 코드로 다양한 클래스 자료형을 대상으로 동작하는 다형성을 활용하는 것이다.

9.4 final 예약어

final : 더 이상 수정할 수 없다는 뜻의 예약어

final 사용 위치 설명
변수 상수를 의미
메소드 하위 클래스에서 재정의 불가능
클래스 상속 불가능(변수나 메소드가 재정의되면 클래스 내용이 변하고 기능 오류 가능성이 있어서)

아래 c언어의 코드처럼 상수를 선언할 때 final 예약어를 사용한다고 보면 된다.
만약 NUM의 값을 바꿔야할 때 한 번만 바꾸면 지금까지 사용한 NUM 변수값이 한번에 바뀌는 장점이 있다.

보통 여러 파일에서 공유해야 하는 상수 값은 아래와 같이 public static final로 선언하여 사용한다.

#define NUM 100
public class Define {
    public static final int NUM = 1;
}

public static final으로 선언한 변수는 다른 파일에서 아래처럼 '클래스.변수 이름'으로 참조 가능

```
public class UsingDefine {
System.out.println(Define.NUM); // 클래스.변수 이름