하나 이상의 추상 메소드를 포함하는 경우 이것을 추상 클래스라고 말합니다. 다만 추상 클래스에 반드시 추상 메소드가 있어야 하는 것은 아닙니다.
추상 클래스는 abstract로 정의되어 사용됩니다.
추상 메소드는 선언만 되어 있고 구현되어 있지 않은 메소드를 말합니다. 그렇기 때문에 추상 클래스는 인스턴스화가 불가능합니다.
여기 리그오브레전드라는 게임을 예제로 만들어봤습니다.
게임 리그오브레전드는 플레이할 챔피언과 스펠이라는 옵션을 선택해 플레이하게 된다. 이 예제에서는 탑, 미드, 바텀 포지션 클래스를 만들고 Champion 클래스를 상속받도록 해보겠습니다.
Champion이라는 추상 클래스에는 공통 메소드와 구현이 필요한 메소드가 존재합니다.
// 추상 클래스
abstract class Champion {
// 공통 메소드
public void checkSpell() {
System.out.println("스펠을 체크한다.");
}
// 자식 클래스에서 구현해야 하는 추상 메소드
public abstract void chooseSpell();
}
게임에서는 반드시 스펠이라는 옵션을 선택해야 하는데 이것을 체크하는 공통 메소드checkSpell()를 정의합니다.
그리고 챔피온마다 다르게 스펠을 선택할 수 있는chooseSpell()이라는 추상 메소드를 정의했습니다.
이제 Champion을 상속받는 각각의 포지션 클래스를 만들어 보겠습니다.
추상 클래스는 extends 키워드를 통해 상속 받습니다. chooseSpell() 메소드를 구현해 포지션에 따라 다른 스펠을 선택하도록 했습니다.
// 탑 클래스
class Top extends Champion {
@Override
public void chooseSpell() {
System.out.println("순간이동");
}
}
// 미드 클래스
class Mid extends Champion {
@Override
public void chooseSpell() {
System.out.println("점멸");
}
}
// 바텀 클래스
class Bottom extends Champion {
@Override
public void chooseSpell() {
System.out.println("회복");
}
}
extends를 통해 상속받은 클래스는 반드시 추상 메소드 chooseSpell()를오버라이딩해야 합니다.
실행을 해보면 공통적인 기능을 공유하면서 일부 메소드(chooseSpell())은 자식 클래스에서 다르게 사용할 수 있는 것을 확인할 수 있습니다.
public class LgOfLegendDemo {
public static void main(String[] args) {
Top top = new Top();
Mid mid = new Mid();
Bottom bottom = new Bottom();
top.checkSpell(); // 스펠을 체크한다.
top.chooseSpell(); // 순간이동
mid.checkSpell(); // 스펠을 체크한다.
mid.chooseSpell(); // 점멸
bottom.checkSpell(); // 스펠을 체크한다.
bottom.chooseSpell(); // 회복
}
}
인터페이스
인터페이스는 추상 메소드와 상수로 이루어져 있습니다. 추상 메소드(abstract)와 상수(public static final)는 컴파일시 자동 인식을 해주기 때문에 키워드를 생략해도 됩니다.
*JAVA8 이상에서는 default 키워드를 통해 일반 메소드 선언이 가능해졌다.
인터페이스는 클래스가 구현해야 하는 메소드의 청사진을 정의합니다.
자바에서는 클래스의 다중 상속이 불가능하지만 인터페이스를 통해 다중 상속을 구현할 수 있습니다.
인터페이스는 implements라는 키워드를 통해 상속 받습니다. chooseChampion(), chooseSpell()을 구현해 플레이어가 원하는 게임을 시작할 수 있습니다.
class Player implements Champion, Spell {
@Override
public void chooseChampion() {
System.out.println("챔피온: 티모");
}
@Override
public void chooseSpell() {
System.out.println("스펠: 순간이동");
}
}
실행을 해보면 두 인터페이스를 통해 각각의 메소드를 구현함으로써 플레이어가 원하는 게임을 할 수 있는 것을 확인할 수 있습니다.
public class Main {
public static void main(String[] args) {
Player player = new Player();
player.chooseChampion(); // 챔피온: 티모
player.chooseSpell(); // 스펠: 순간이동
}
}
정리
추상 클래스와 인터페이스의 공통점/차이점을 정리하면 다음과 같습니다.
공통점
상속과 오버라이딩을 통해 자식 클래스에 구현을 한다.
차이점
추상 클래스는 단일 상속만 가능하지만, 인터페이스는 다중 상속이 가능하다.
추상 클래스는 일부 메소드의 구현을 포함할 수 있지만, 인터페이스는 모든 메소드가 추상 메소드이다.
추상 클래스는 extends 키워드를 사용하고, 인터페이스는 implements 키워드를 사용한다.
추상 클래스와 인터페이스는 상속과 오버라이딩을 통해 자식 클래스에 구현을 한다는 공통된 점이 있습니다. 그럼에도 불구하고 추상 클래스와 인터페이스를 나눠 사용하는 이유는 무엇일까요? 추상 클래스와 인터페이스는 사용 목적에 차이가 있다고 생각합니다.
공통적인 동작을 여러 클래스에 공유할 때는 추상 클래스를 사용하고, 클래스가 구현해야 하는 기능을 정의하고 다중 상속을 허용해야 할 때, 인터페이스를 사용합니다.
추상 클래스는 "어떤 것(is-a)"을 정의하고 인터페이스는 "하는 일(can-do)"의 관계를 정의함으로써 사용 목적에 맞게 추상 클래스와 인터페이스를 구분한다면, 더 나은 구조와 유연성을 가진 소프트웨어를 개발할 수 있을 것입니다.