Do it! 자바 프로그래밍 입문 / 9장 추상 클래스
9.1 추상 클래스란?(+ 추상 클래스를 만드는 이유)
상속받는 클래스를 위해 만드는 클래스이다.
즉, 포괄 개념의 상속 클래스에게 각자 다른 기능을 하는 공통의 추상 메소드를 상속해주기 위해 만드는 클래스이다.
추상 메소드 display()가 있다고 가정할 때 DeskTop과 NoteBook이 display() 함수를 상속받고 각자 다른 기능으로 공통의 display() 함수를 사용하는 것이다.
- 추상 클래스는 항상 추상 메소드를 포함한다.
- 추상 메소드의 구현 코드가 없으므로 추상 클래스의 인스턴스를 생성할 수 없다.
- 형 변환은 할 수 있다.
추상 메소드란?
- 상속받는 클래스들마다 각자 구현 코드를 작성해서 각자 다른 기능으로 사용
- 구현 코드와 함수의 몸체('{}')가 없고 ';'를 이용해서 선언한다.
- 상속받는 클래스에서 추상 메소드의 구현 코드를 작성하기 때문에 같은 메소드지만 내용은 각자 다르다.
즉, 상속받은 클래스마다 같은 함수를 각자 다른 기능으로 사용하려고 만드는 것이 추상 메소드다.
public abstract class Computer // 추상 클래스
{
public abstract int display(); // 추상 메소드
public abstract int typing(); // 추상 메소드
}
위 추상 클래스를 상속받는 클래스는 두 가지로 나뉜다.
- 추상 메소드를 구현하지 않은 추상 클래스(일부 구현은 가능)
- 추상 메소드를 모두 구현한 일반 클래스
// 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); // 클래스.변수 이름