Chapter 9 추상 클래스

Ruinak·2021년 5월 29일
0

Java

목록 보기
9/15
post-thumbnail

1. 추상 클래스

1-1 추상 클래스란?

  • 추상적이란 것은 구체적이지 않고 막연한 것을 뜻합니다.
  • '어떤 클래스가 추상적이다'라는 말은 '구체적이지 않은 클래스'라는 뜻입니다.
  • 추상 클래스를 영어로 표현하면 abstract class, 추상 클래스가 아닌 클래스를 concrete class라고 합니다.
  • 우리가 지금까지 만든 클래스는 모두 concrete class였습니다.
  • 추상 클래스는 항상 추상 메서드를 포함하며 구현 코드가 없습니다.
  • 구현 코드가 없다는 것은 함수 몸체(body)가 없다는 뜻입니다.
  • 중괄호 { }로 감싼 부분을 함수의 구현부(implementation)라고 합니다.
  • 이 부분이 없는 함수는 추상 함수(abstract function)이고 자바에서는 추상 메서드(abstract method)라고 합니다.
  • 추상 메서드는 다음과 같이 선언만 하며 abstract 예약어를 사용하며, { } 대신 ;를 사용합니다.
  • 정리하면, 자바에서 추상 메서드는 abstract 예약어를 사용하여 선언한 메서드입니다.

메서드 선언의 의미

  • 로직을 구현하는 것보다 더 중요한 것이 어떻게 구현할지를 결정하는 것입니다.
  • 이런 과정을 개발 설계라고 합니다.
  • 물론 설계 과정은 더 복잡하고 다양한 방법이 있을 수 있습니다.
  • 위 코드처럼 선언한 메서드를 보면 두 개의 정수를 입력 받은 후 더해서 그 결과 값을 반환한다는 것을 유추할 수 있습니다.
  • 즉 이 메서드의 선언부(declatration)만 봐도 어떤 일을 하는 메서드인지 알 수 있습니다.
  • 함수의 선언부 즉 반환 값, 함수 이름, 매개 변수를 정의한다는 것은 곧 함수의 역할이 무엇인지, 어떻게 구현해야 하는지를 정의한다는 뜻입니다.
  • 따라서 함수 몸체를 구현하는 것보다 중요한 것은 함수 선언부를 작성하는 것입니다.
  • 이와 마찬가지로 자바에서 메서드를 선언한다는 것은 메서드가 해야 할 일을 명시해 두는 것입니다.

1-2 추상 클래스 구현하기

예제 9-1 추상 클래스 구현하기

public class Computer {

	// 오류 발생
	public void display();
	public void typing();
	
	public void turnOn() {
		System.out.println("전원을 켭니다.");
	}

	public void turnOff() {
		System.out.println("전원을 끕니다.");
	}
}
  • 클래스 내부에 추상 메서드 display( )와 typing( )을 선언하고, 구현 메서드 turnOn( )과 turnOff( )를 작성합니다
  • 완전하게 구현하지 않은 두 추상 메서드에서 오류가 발생합니다.
  • display( )나 typing( ) 위에 마우스를 올리면 오류를 해결할 수 있는 방법이 뜨고, 두번째 옵션을 선택하면 display( )와 typing( )에 abstract 예약어가 생깁니다.
// 오류 발생
public class Computer {
	
	// abstract 추가했지만 오류가 남아있음
	public abstract void display();
	public abstract void typing();
	
	......
    
}
  • 하지만 이번에는 메서드와 클래스 이름에 모두 오류가 표시됩니다.
  • 추상 메서드가 속한 클래스를 추상 클래스로 선언하지 않았기 때문입니다.
  • 클래스도 추상 클래스로 만들면 해결이 됩니다.
// abstract 추가
public abstract class Computer {
	
	// abstract 추가
	public abstract void display();
	public abstract void typing();
	
	public void turnOn() {
		System.out.println("전원을 켭니다.");
	}

	public void turnOff() {
		System.out.println("전원을 끕니다.");
	}
}
  • Computer 클래스를 이와 같이 구현한 것은 'Computer를 상속받는 클래스 중 turnOn( )과 turnOff( ) 구현 코드는 공통이다.
  • 하지만 display( )와 typing( )은 하위 클래스에 따라 구현이 달라질 수 있습니다.
  • 그래서 Computer에서는 구현하지 않고, '이 두 메서드 구현에 대한 책임을 상속받는 클래스에 위임한다'라는 의미입니다.
  • 따라서 Computer 클래스의 추상 메서드는 추상 클래스를 상속받은 DeskTop과 NoteBook에서 실제로 구현하게 됩니다.
  • 상위 클래스에서는 하위 클래스도 공통으로 사용할 메서드를 구현하고, 하위 클래스마다 다르게 구현할 메서드는 추상 메서드로 선언해 두는 것입니다.

예제 9-2 추상 메서드 구현하기

public class DeskTop extends Computer {

	@Override
	public void display() {
		// 추상 메서드 몸체 작성
		System.out.println("DeskTop display()");
	}

	@Override
	public void typing() {
		// 추상 메서드 몸체 작성
		System.out.println("DeskTop typing()");		
	}
}
  • 추상 클래스를 상속받은 하위 클래스는 구현되지 않은 추상 메서드를 모두 구현해야 구체적인 클래스가 됩니다.
  • DeskTop은 추상 메서드를 모두 구현했으므로 추상 클래스는 아닙니다.

예제 9-3 NoteBook 클래스 구현하기

public abstract class NoteBook extends Computer {

	@Override
	public void display() {
		System.out.println("NoteBook display()");
	}
}
  • 상속받은 추상 메서드를 모두 구현하지 않았습니다.
  • 추상 메서드를 모두 구현하지 않아, 추상 메서드가 남았기 때문에 NoteBook은 추상 클래스가 됩니다.

예제 9-4 MyNoteBook 클래스 구현하기

public class MyNoteBook extends NoteBook {

	@Override
	public void typing() {
		System.out.println("MyNoteBook typing()");
	}
}
  • MyNoteBook은 typing( ) 메서드만 구현했습니다.
  • NoteBook을 상속받았으므로 display( )까지 구현이 된 것으로 모든 추상 메서드가 구현된 클래스이므로 abstract 예약어를 사용하지 않아도 됩니다.

1-3 추상 클래스를 만드는 이유

예제 9-5 추상 클래스 테스트하기

public class ComputerTest {
	public static void main(String[] args) {
		
		// Computer에서 오류가 발생 - 클래스를 인스턴스로 생성 불가
		Computer c1 = new Computer();
		Computer c2 = new DeskTop();
		// NoteBook에서 오류가 발생 - 클래스를 인스턴스로 생성 불가
		Computer c3 = new NoteBook();
		Computer c4 = new MyNoteBook();
	}
}
  • Computer 클래스형으로 인스턴스를 4개 생성하였으나, Computer와 NoteBook에서 클래스를 인스턴스로 생성할 수 없다는 오류가 발생합니다.
  • 추상 클래스는 모든 메서드가 구현되지 않았으므로 인스턴스로 생성할 수 없습니다.

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

  • 추상 클래스는 상속을 하기 위해 만든 클래스입니다.
  • 추상 클래스에서 구현하는 메서드는 하위 클래스에서도 사용할, 즉 하위 클래스에서도 구현 내용을 공유할 메서드를 구현합니다.
  • 실제 하위 클래스에서 내용을 다르게 구현해야 한다면, 구현 내용을 추상 메서드로 남겨 두고 하위 클래스에 구현을 위임하는 것입니다.

2. 템플릿 메서드

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

  • '템플릿(template)'이란 틀이나 견본을 뜻하므로, 템플릿 메서드란 틀이 있는 메서드라는 의미입니다.
  • 앞선 클래스와 객체 파트에서 소개한 싱글톤 패턴과 같은 디자인 패턴입니다.
  • 템플릿 메서드는 추상 클래스를 사용하여 구현할 수 있습니다.

예제 9-6 추상 클래스와 템플릿 메서드(1)

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개의 구현된 메서드 startCar( ), turnOff( ), run( )을 가지고 있습니다.
  • 자동차의 시동을 켜거나 끄는 방법은 어느 차에서나 동일하므로, startCar( )와 trunOff( )는 미리 코드를 구현했습니다.
  • drive( )와 stop( )은 차종에 따라 다른 방식으로 움직일 수 있으므로 추상 메서드로 선언합니다.

예제 9-7 추상 클래스와 템플릿 메서드(2)

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는 자율주행을 하고 방향도 알아서 바꿈, 사람은 시동을 켜고 끄는 일만 하면 됩니다.

예제 9-8 추상 클래스와 템플릿 메서드(3)

public class ManualCar extends Car {

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

	@Override
	public void stop() {
		System.out.println("브레이크로 정지합니다.");		
	}
}
  • ManualCar 클래스는 Car 클래스를 상속 받았고 drive( )와 stop( ) 추상 메서드를 구현했습니다.
  • 사람이 직접 핸들을 조작하면서 달리고 있으며, 정지를 하려면 브레이크를 밟으면 됩니다.

예제 9-9 추상 클래스와 템플릿 메서드(4)

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

2-2 템플릿 메서드의 역할

  • CarTest에서 두 개의 인스턴스(AICar, ManualCar)를 생성하고 run( )을 호출했습니다.
  • run( )은 Car 클래스에 이미 구현된 메서드입니다.
  • 템플릿 메서드의 역할은 메서드 실행 순서와 시나리오를 정의하는 것입니다.
  • 템플릿 메서드에서 호출하는 메서드가 추상 메서드라면 차종에 따라 구현 내용이 바뀔 수 있습니다.
  • 공통적인 시동을 켜고, 달리고, 멈추고, 시동을 끄는 시나리오는 변하지 않습니다.
  • 템플릿 메서드는 실행 순서, 즉 시나리오를 정의한 메서드이므로 바뀔 수 없습니다.
  • 상위 클래스를 상속 받은 하위 클래스에서 템플릿 메서드를 재정의하면 안 된다는 의미입니다.
  • 그러므로 final 예약어를 사용해 선언합니다.
  • 템플릿 메서드는 로직 흐름이 이미 정해져 있는 프레임워크에서 많이 사용하는 기본 구현 방법입니다.

3. 템플릿 메서드 응용하기

3-1 클래스 기능과 관계

  • 무조건 클래스를 만들어 코딩하는 것보다 주어진 문제를 어떻게 해결할 것인지를 천천히 생각해 보고 손으로 클래스 다이어그램을 간략하게 그려 보는 것이 객체 지향 방식으로 문제를 해결하는 좋은 습관입니다.
  • 큰 프로젝트를 진행할 때는 이 과정을 분석 · 설계 과정이라고 함

3-2 클래스 설계하기

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

Player 클래스

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

예제 9-10 Player 클래스 구현하기

public class Player {
	// Player가 가지는 level 변수 선언
	private PlayerLevel level;			
	
	// 디폴트 생성자. 처음 생성되면 BeginnerLevel로 시작하며 레벨 메세지 출력
	public Player() {					
		level = new BeginnerLevel();
		level.showLevelMessage();
	}	
	
	public PlayerLevel getLevel() {
		return level;
	}
	
	// 레벨 변경 메서드, 현재 자신의 level을 매개변수로 받은 level로 변경하고 레벨 메세지 출력
	// 매개변수 자료형은 모든 레벨로 변환 가능한 PlayerLevel
	public void upgradeLevel(PlayerLevel level) {	
		this.level = level;
		level.showLevelMessage();
	}	
	
	// PlayerLevel의 템플릿 메서드 go() 호출
	public void play(int count) {
		level.go(count);			
	}	
}

PlayerLevel 클래스

  • 각 레벨에서 수행할 공통 기능은 PlaberLevel 추상 클래스에서 선언함

예제 9-11 PlayerLevel 추상 클래스 구현하기

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

초보자 레벨 클래스

  • 초보자 레벨에서는 천천히 달릴 수만 있습니다.
  • 점프나 턴은 할 수 없습니다.

예제 9-12 초보자 레벨 클래스 구현하기

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("===== 초보자 레벨입니다 =====");
	}
}

중급자 레벨 클래스

  • 중급자 레벨에서는 빠르게 달릴 수 있고 높이 점프할 수 있습니다.
  • 아직 턴하는 기술은 사용하지 못합니다.

예제 9-13 중급자 레벨 클래스 구현하기

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

고급자 레벨 클래스

  • 고급자 레벨에서는 엄청 빠르게 달릴 수 있고 아주 높게 점프할 수 있습니다.
  • 한바퀴 턴하는 기술까지 사용할 수 있습니다.

예제 9-14 고급자 레벨 클래스 구현하기

package study.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("한 바퀴 돕니다.");
	}

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

테스트 프로그램 작성해서 실행하기

예제 9-15 테스트 프로그램 실행하기

public class MainBoard {
	public static void main(String[] args) {
		// 객체 생성. 처음 생성하면 BeginnerLevel
		Player player = new Player();		
		player.play(1);						
		
		AdvancedLevel aLevel = new AdvancedLevel();
		player.upgradeLevel(aLevel);
		// 2이므로 점프 2번
		player.play(2);						
		
		SuperLevel sLevel = new SuperLevel();
		player.upgradeLevel(sLevel);
		// 3이므로 점프 3번
		player.play(3);						
	}
}

  • Player 클래스의 디폴트 생성자는 초보자 레벨로 시작하도록 구현되어 있습니다.
  • Player 클래스를 생성하고 인스턴스를 player 참조 변수에 대입합니다.
  • play( ) 메서드의 매개변수에 1을 입력하면 초보자 레벨이기 때문에 천천히 달린 후 Jump할 줄 모른다는 문구와 Turn할 줄 모른다는 문구를 출력합니다.

3-3 추상 클래스와 다형성

  • 상위 클래스인 추상 클래스는 하위에 구현된 여러 클래스를 하나의 자료형(상위 클래스 자료형)으로 선언하거나 대입할 수 있습니다.
  • 추상 클래스에 선언된 메서드를 호출하면 가상 메서드에 의해 각 클래스에 구현된 기능이 호출됩니다.
  • 하나의 코드가 다양한 자료형을 대상으로 동작하는 다형성을 활용할 수 있는 것입니다.
  • 모든 추상 클래스에 템플릿 메서드를 사용하는 것은 아니지만, 추상 클래스를 활용할 수 있는 좋은 패턴입니다.

4. final 예약어

  • final 예약어는 마지막으로 정한 것이니 더 이상 수정할 수 없다는 뜻입니다.
  • 자바 프로그램에서 final 예약어는 변수, 메서드, 클래스에 사용할 수 있습니다.

4-1 상수를 의미하는 final 변수

  • 상수는 변하지 않는 수입니다.

예제 9-16 final 실습하기

public class Constant {
	int num = 10;
	// 상수 선언
	final int NUM = 100;
	
	public static void main(String[] args) {
		Constant cons = new Constant();
		cons.num = 50;
		// 상수에 값을 대입하여 오류 발생
		// cons.NUM = 200;		
		
		System.out.println(cons.num);
		System.out.println(cons.NUM);
	}
}
  • 두 개의 변수 int num과 final int NUM을 선언합니다.
  • 변수 이름은 대소문자를 구별하기 때문에 두 변수는 다른 변수입니다.
  • 상수를 선언할 때는 일반 변수와 구별하기 위해 대문자로 쓰는 경우가 많습니다.
  • 상수로 선언한 NUM에는 다른 값을 대입할 수 없기 때문에 새로운 값을 대입하면 오류가 발생합니다.

여러 자바 파일에서 공유하는 상수 값 정의하기

  • 하나의 자바 파일에서만 사용하는 상수 값은 해당 파일 안에서 정의해서 사용할 수 있습니다.
  • 자바로 프로젝트를 진행할 때 여러 파일에서 공유해야 하는 상수 값은 한 파일에 모아서 public static final로 선언하여 사용하면 좋습니다.

예제 9-17 여러 파일에서 공유하는 상수

public class Define {
	public static final int MIN = 1;
	public static final int MAX = 99999;
	public static final int ENG = 1001;
	public static final int MATH = 2001;
	public static final double PI = 3.14;
	public static final String GOOD_MORNING = "Good Morning";
}
  • 상수를 모두 public 예약어로 선언했으므로 이 값들은 외부에서도 사용할 수 있습니다.
  • 모든 상수를 static으로 선언했기 때문에 인스턴스를 생성하는 것과 관계없이 클래스 이름으로 참조할 수 있습니다.

예제 9-18 상수 사용하기

public class UsingDefine {
	public static void main(String[] args) {
		// static으로 선언했으므로 인스턴스를 생성하지 않고 클래스 이름으로 참조 가능
		System.out.println(Define.GOOD_MORNING);	
		
		System.out.println("최솟값은 " + Define.MIN + "입니다.");
		System.out.println("최댓값은 " + Define.MAX + "입니다.");
		System.out.println("영어 과목 코드 값은 " + Define.ENG + "입니다.");
		System.out.println("수학 과목 코드 값은 " + Define.MATH + "입니다.");
	}
}

4-2 상속할 수 없는 final 클래스

  • 클래스는 final로 선언하면 상속할 수 없습니다.
  • 상속을 하면 변수나 메서드를 재정의할 수 있는데, 그러면 원래 클래스가 가지고 있는 기능에 오류가 생길 수도 있습니다.
  • 보안과 관련되어 있거나 기반 클래스가 변하면 안 되는 경우에는 클래스를 final로 선언합니다.

4-3 프로그램을 잘 구현하는 또 다른 방법

  • 책 내용을 따라 코딩을 해보았다면, 거꾸로 진행해 보는 방법이 있습니다.
  • 테스트 코드를 먼저 개발하는 방법론을 테스트 주도 개발(Test Driven Development: TDD)라고 합니다.
profile
Nil Desperandum <절대 절망하지 마라>

0개의 댓글