데이터융합 JAVA응용 SW개발자 기업 채용연계 연수과정 7일차 강의 정리

misung·2022년 3월 31일
0

09 추상 클래스

09-1 추상 클래스

추상 클래스란?

추상 클래스는 abstract class 라 하며, 여태 지금까지 만들어 온 클래스는 concrete class였다.

추상 클래스의 경우 항상 추상 메서드를 포함하는데, 추상 메서드는 구현 코드가 없다.

abstract int add(int x, int y);

추상 메서드는 abstract 예약어를 쓰고 함수 구현부가 없어 ;로 끝난다.

메서드 선언의 의미

int add(int num1, int num2);

위 코드 같은 경우 두 정수를 받아 더한 값을 반환한다는 의미를 갖는다는 것을 어렴풋이 알 수 있다.

함수 반환값, 함수 이름, 매개변수를 정의하는 것은 함수의 역할과 구현해 대해 정의하는 것이므로 함수 몸체를 구현하는 것 보다 중요한 것은 선언부의 작성임을 알 수 있다.

추상 클래스 구현하기

display(), typing() 두 가지의 추상 메서드를 갖고
turnOn(), turnOff() 메서드를 가지는 Computer 클래스를 구현하고, 이를 상속받는 Desktop 을 구현하고,
이 중 display() 만 구현한 추상 클래스Laptop
Laptop 클래스를 상속받아 모든 메서드를 구현한
MyLaptop 을 구현해 보자.

Computer.java

package abstractex;

public abstract class Computer {
	public abstract void display();
	public abstract void typing();
	public void turnOn() {
		System.out.println("전원을 켠다.");
	}
	public void turnOff() {
		System.out.println("전원을 끈다.");
	}
}

Desktop.java

package abstractex;

public class Desktop extends Computer {
	
	@Override
	public void display() {
		System.out.println("desktop display()");
	}
	
	@Override
	public void typing() {
		System.out.println("desktop typing()");
	}
}

Laptop.java

package abstractex;

public abstract class Laptop extends Computer {	
	@Override
	public void display() {
		System.out.println("laptop display()");
	}
}

MyLaptop.java

package abstractex;

public class MyLaptop extends Laptop {
	@Override
	public void typing() {
		System.out.println("mylaptop typing");
	}
}

turnOn()turnOff() 는 모든 클래스에 공통이지만, display()typing() 은 하위 클래스에 따라 구현이 달라진다.

이렇게 하위 클래스마다 다르게 구현할 메서드는 추상 메서드로 선언해 둔다.

Computer 클래스의 경우 모든 메서드를 구현하고,
Laptop 클래스의 경우 모든 메서드를 구현하지 않았기 때문에 추상 클래스가 되어야 한다.
MyLaptop 클래스는 모든 추상 메서드가 구현되었으니 추상 클래스가 아니다.

나 혼자 코딩

조금 더 개념을 이해하기 위해서 한번 해보자.

Car.java

package vehicle;

public abstract class Car {
	public abstract void refuel();
	public abstract void run();
	
	public void stop() {
		System.out.println("차가 멈춥니다.");
	}
}

Bus.java

package vehicle;

public class Bus extends Car {

	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
	
	@Override
	public void refuel() {
		System.out.println("천연 가스를 충전합니다.");
	}
	
	public void takePassenger() {
		System.out.println("승객을 버스에 태웁니다.");
	}
}

AutoCar.java

package vehicle;

public class AutoCar extends Car{

	@Override
	public void refuel() {
		System.out.println("휘발유를 주유합니다.");
	}

	@Override
	public void run() {
		System.out.println("차가 달립니다.");
	}
	
	public void load() {
		System.out.println("짐을 싣습니다.");
	}
}

CarTest.java

package vehicle;

public class CarTest {

	public static void main(String[] args) {
		Bus bus = new Bus();
		AutoCar autoCar = new AutoCar();
		
		bus.run();
		autoCar.run();
		
		bus.refuel();
		autoCar.refuel();
		
		bus.takePassenger();
		autoCar.load();
		
		bus.stop();
		autoCar.stop();
	}

}

refuel()run() 은 각각 상속받는 클래스에서 재정의할 수 있게 추상 클래스로 남기고, stop() 의 경우 공통으로 사용할 것이므로 일반 메서드로 선언.

Bus 클래스에서는 takePassenger() 라는 메서드를 따로 갖고,
AutoCar 클래스에서는 load() 라는 메서드를 따로 갖는다.

추상 클래스를 만드는 이유

추상 클래스를 어디에 어떻게 사용하는지 알기 위해 아까 만든 Computer 및 하위 클래스들을 테스트하기 위한 ComputerTest 클래스를 만들고 실습해보자.

ComputerTest.java

package abstractex;

public class ComputerTest {

	public static void main(String[] args) {
		Computer c1 = new Computer();
		Computer c2 = new Desktop();
		Computer c3 = new Laptop();
		Computer c4 = new MyLaptop();
	}

}

작성해 보면 c1, c3는 생성할 수 없다. 왜냐하면 Computer 클래스와 Laptop 클래스는 추상 클래스이기 때문이다.

각 클래스에는 구현되지 않은 코드들이 있으므로, 인스턴스로 만들 수 없는 것이다.

추상 클래스에서 구현하는 메서드

추상 클래스에 구현하는/구현하지 않는 메서드의 구분은 어떻게 할까?

turnOn() 이나 turnOff() 같은 경우 하위 클래스에서 공유 가능하나, display()typing() 등은 하위 클래스에 의해 달라질 수 있으므로 (Laptop인지 Desktop인지) 구현하지 않도록 한 것이다.

09-2 템플릿 메서드

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

추상 클래스를 활용한 예로 템플릿 메서드가 있다.
템플릿 메서드는 추상 클래스를 사용해 구현할 수 있다.

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

위의 run() 메서드가 템플릿 메서드인데, Car 클래스를 상속받는 하위 클래스들은 모두 이 동일한 순서대로 작동하게 될 것이다.

이를 상속받을 클래스 두 개 AICar, ManualCar를 각각 구현해 보자.

AICar.java

package template;

public class AICar extends Car{
	@Override
	public void drive() {
		System.out.println("자율 주행을 한다.");
		System.out.println("자동차가 스스로 방향 전환을 한다.");
	}
	
	@Override
	public void stop() {
		System.out.println("스스로 멈춘다.");
	}
}

ManualCar.java

package template;

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

CarTest.java

package template;

public class CarTest {

	public static void main(String[] args) {
		System.out.println("===자율주행 자동차===");
		Car myCar = new AICar();
		myCar.run();
		
		System.out.println("===사람이 운전하는 자동차===");
		Car hisCar = new ManualCar();
		hisCar.run();
	}

}

출력 :
===자율주행 자동차===
시동을 겁니다.
자율 주행을 한다.
자동차가 스스로 방향 전환을 한다.
스스로 멈춘다.
시동을 끕니다.
===사람이 운전하는 자동차===
시동을 겁니다.
사람이 운전한다.
사람이 핸들을 조작한다.
브레이크로 정지한다.
시동을 끕니다.

템플릿 메서드의 역할

그래서 템플릿 메서드의 역할이 무엇이냐 하면, 메서드의 실행 순서와 시나리오 를 정의하는 것이다.

여기서 약간 헷갈려서 같은 개념의 다른 구현들을 찾아봤는데, final 예약어를 사용한 경우도 있고 아닌 경우도 있긴 했다. 일단 책의 서술에 따르면 시나리오의 정의이므로 final 예약어를 사용하여 재정의할 수 없게 했다고 한다.

09-3 템플릿 메서드 응용하기

템플릿 메서드에 대한 이해를 돕기 위해 예제를 만들어 보자.

Player 라는 클래스를 생성하고, 이 클래스는 Player가 가지는 레벨에 따라 할 수 있는 세 가지 run(), jump(), turn() 기능을 갖는다.

그리고 모든 레벨에서 Player 가 사용할 수 있는 go(int count) 메서드를 제공한다.
해당 메서드는 한 번의 run, count 만큼 jump, 한 번 turn 한다.
만약 해당 레벨에서 불가능한 동작인 경우 불가능하다고 출력한다.

클래스 기능과 관계

UML을 이용하여 클래스 다이어그램을 먼저 그리고 프로그래밍에 임하는 것이 좋다.

Player레벨에 따라 구현할 기능을 다르게 해 줄 것인데, 만약 클래스에서

if (level == beginner) {
	...
} else if (level == advanced) {
	...
} else if (...) {
	...
}

이런 식으로 구현하는 경우 기능이 추가거나 레벨이 추가되기라도 하면 코드를 자꾸 늘려야 한다.

클래스 설계하기

Player 클래스와 PlayerLevel 클래스는 포함 (HAS-A) 관계이다.

모든 Player는 자신의 레벨을 가지고 있고 레벨에 따라 수행할 수 있는 기능이 달라지므로 PlayerLevel 클래스를 추상 클래스로 만들고, 모든 레벨에서 가능한 공통 기능과 각 레벨에서 달라지는 기능은 추상 클래스로 구현하자.

코드가 길어져서 gist로 분리
https://gist.github.com/crisine/517d1cfcfb8d09a5d856fba318317d02

Player 클래스의 upgradeLevel(PlayerLevel level) 메서드가 좀 이해가 안 가긴 했는데, 원래 게임이라면 EXP나 특정 조건을 만족해야 레벨업을 시켜주지만 여기서는 그냥 대입으로 레벨을 '바꿔' 주는 식이라고 생각하면.. 맞는 것 같기도 하다.

player는 처음에 Beginner 로 타입을 정한다. 생성자에서 Beginner 레벨로 시작하도록 되어 있으므로..

이후 AdvancedLevel SuperLevel 클래스에 대한 인스턴스를 각각 선언하고 player 인스턴스의 upgradeLevel 메소드를 호출하고 매개 변수로 방금 만든 인스턴스를 넣어 레벨을 올려(바꿔)준다.

추상 클래스와 다형성

Player 가 가질 수 있는 여러 PlayerLevel 을 별도의 자료형으로 선언하지 않고 여기서는 PlayerLevel 로 선언하였다. 레벨 변경 메소드의 매개변수 또한 PlayerLevel 이므로 레벨 클래스가 여러 개 존재한다 한들, PlayerLevel 클래스로 대입될 수 있다.

09-4 final 예약어

final 예약어는 변수, 메서드, 클래스 등에 사용 가능하다.

변수에 사용 : 상수화
메서드에 사용 : 하위 클래스에서 재정의 불가
클래스에 사용 : 상속 불가

여러 파일에서 공유하는 상수 값 (사용예)

public class Define {
	public static final int MIN = 1;
    public static final int MAX = 100;
    public static final double PI = 3.141592;
}

...

main method() {
	sysout(Define.PI);
}

static으로 변수 선언 후 상수화하였으므로 인스턴스를 생성하지 않고도 참조할 수 있다.

0개의 댓글