Skip to content

Day 15(21.09.27)

Byeong Gwan Seo edited this page Sep 27, 2021 · 1 revision

22. 추상 클래스(Abstract Class)

(1) 추상 클래스란 무엇인가?

추상 클래스란 무엇인가를 얘기하기 전에 이 질문에 대해 생각해 보자. "자동차란 무엇인가?". 굉장히 "추상적"이지 않은가? 우리가 흔히 자동차라고 부르는 것에는 세단, 트럭, 컨버터블 등의 차종이 있다. 여기서 이러한 차종들은 자동차라는 개념이 지닌 공통적인 특성을 가지고 구체적인 형태로 드러나게 된 것이다.
이와 마찬가지로 마찬가지로 객체 지향 개발에서의 클래스에서도 추상 클래스라는 것이 존재한다. 객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 한다면 이들의 공통되는 특성을 추출해서 선언한 클래스를 추상(Abstract) 클래스라고 한다. 논리적 관계를 생각했을때, 이러한 추상 클래스와 실체 클래스는 상속 관계를 가지고 있다는 것을 알 수 있다. 즉, 실체 클래스가 추상 클래스를 상속 받아, 추상 클래스의 모든 특성을 물려받고(상속), 추가적인 특성을 가질 수 있게 되는 것이다.(오버라이드). 위의 자동차 이야기로 생각을 해보면, 스포츠카, 트럭, 택시 등이 실체 클래스이고, 자동차라는 포괄적인 개념이 추상 클래스가 되며, 실체 클래스가 지닌 공통적인 요소를 추상 클래스에서 상속받아 사용할 수 있게 되는 것이라고 보면 된다.
물론 추상 클래스와 기본적인 부모 클래스 역할의 차이는, 추상 클래스는 문자 그대로 추상적인 개념을 담고 있기 때문에 그 실체가 담겨 있지 않다. 다시 말해서, 실체가 되는 객체를 생성할 수 없게되며, new 연산자를 사용한 인스턴스 생성이 불가능하다. 때문에 추상 클래스는 실체 클래스를 만들기 위한 부모 클래스의 역할 외에는 할 수 없다. 상속 관계 선언 코드로 살펴보자면 ~ extends 뒤에만 올 수 있는 클래스라는 뜻이다.

class Taxi extends Car (O)
class Cat extends Pet (O)
class Rifle extends Weapon (O)

위 예시에선 Car와 Pet, Weapon이 해당되겠다.

(2) 추상 클래스를 사용하는 이유

실체 클래스들의 공통된 필드와 메소드의 이름을 통일하려고

실체 클래스들의 공통적인 특성(필드, 메소드)을 뽑아내어 추상 클래스로 만드는 이유는 크게 두 가지 이유가 있다. 그중 하나는 바로 공통된 필드와 메소드의 이름을 통일하기 위함이다. 예를 들어, 컨버터블의 소유주를 owner라고 하고, 시동을 거는 명령을 turnOn()이라고 했을때, 택시의 소유주를 driver라고 하고, 시동을 거는 명령어가 start()라고 하자. 공통된 특징이지만 필드명과 메소드의 이름이 제각각인 경우인데, 이럴때 Car라는 추상 클래스에 소유주를 owner, 시동을 거는 명령어를 turnOn()이라고 선언하게 된다면, 각 실체 클래스는 이를 상속받아 사용함으로써 필드와 메소드의 이름을 통일할 수 있다.

실체 클래스를 작성할 때 시간을 절약

이는 상속과 동일한 이유이다. 항목의 소유주라는 필드 값과, 시동을 건다는 명령, 메소드가 추상 클래스에서 모두 선언해뒀기 때문에 실체 클래스를 작성하는데 시간을 절약할 수 있다. 이는 다른 기능을 추가한다거나 할때만 재정의 해서 사용하면 되기도 하고, 혹은 다른 프로그래머와 같이 작업을 할때, 추상 클래스 하나만 확인해도 필드와 메소드의 정의를 확인할 수 있게 함으로써 작업 전체의 효율을 올리는데 기여할 수 있게 된다.

(3) 추상 클래스 선언

추상 클래스를 선언할 때에는 클래스를 선언할 때 abstract 키워드를 붙인다. 위에서 말했듯 abstract를 붙이면 인스턴스를 생성할 수 없게 되므로 new 연산자를 사용할 수 없고 상속 받은 자식 클래스를 통해서만 인스턴스를 생성할 수 있다.

public abstract class 클래스명 {
  //field
  //constructor
  //method
}

(4) 추상 메소드와 오버라이딩

아래 예시는 추상 메소드와 추상 메소드의 오버라이딩에 관련된 예시이다.

public class HeroTest {
	public static void main(String[] args) {
		
//영웅 -> 굉장히 추상적인 개념
//슈퍼맨
	SuperMan superman = new SuperMan("슈퍼맨");
	superman.attack();
		
//아이언맨
	IronMan ironman = new IronMan("아이언맨");
	ironman.attack();
		
//헐크		
	Hulk hulk = new Hulk("헐크");
	hulk.attack();	
	}
}
public abstract class Hero {
	String name;
	
	public Hero() {}
	
	public Hero(String name) {
		super();
		this.name = name;
	}
	
	abstract void attack();
}

class SuperMan extends Hero{
	
	public SuperMan() {}
	
	public SuperMan(String name) {
		super(name);
	}
	
	@Override
	void attack() {
		System.out.println("Attack with Eyeball laserbeam");
	}
}

class IronMan extends Hero{
	
	public IronMan() {}
	
	public IronMan(String name) {
		super(name);
	}
	
	@Override
	void attack() {
		System.out.println("Attack with Hand laserbeam");
	}
}

class Hulk extends Hero{
	
	public Hulk() {}
	
	public Hulk(String name) {
		super(name);
	}
	
	@Override
	void attack() {
		System.out.println("Beat the shit out of them");
	}
}

위에서 추상 클래스는 실체 클래스가 공통적으로 가져야 할 필드와 메소드들을 정의해 놓은 추상적인 클래스라고 정의했다. 만약 모든 실체들이 똑같은 행동을 하고, 똑같은 기능만 한다면 추상 클래스에 정의해 놓은 메소드로 모든 것을 한 번에 처리할 수 있을 것이다. 하지만 실제로는 "동물"이라는 추상 클래스가 있다고 할때, 개가 우는 소리와 고양이가 우는 소리가 다르듯, 실행 내용이 실체 클래스마다 달라야 하는 경우가 발생한다. 이럴때는 각 실체 클래스에서 메소드를 재정의해서 사용해야한다. 추상 클래스의 추상 메소드는 일반적인 메소드의 형태와 조금 다른데, 선언부만 있고 메소드의 실행 내용인 중괄호가 없다.

abstract void attack();
// 접근제어자 abstract 리턴타입 메소드명(파라미터1, 파라미터2, ...);

보통 추상 클래스를 설계할때, 하위 클래스가 반드시 실행했으면 하는 실행 내용을 채우도록 강요하고 싶을때 해당 메소드를 추상 메소드로 선언한다. 그리고 자식 클래스는 해당 메소드를 재정의 해서 사용해야 한다. 만약 그러지 않으면 컴파일 에러가 난다. 그러니 모두 부모님이 하는 말을 잘 듣자. 아니면 등짝 맞는다.

Clone this wiki locally