하나 이상의 추상 메소드를 포함하는 경우 이것을 추상 클래스라고 말한다. 다만 추상 클래스에 반드시 추상 메소드가 있어야 하는 것은 아니다. 이러한 추상 클래스는 abstract로 정의되어 사용된다.
그렇다면 추상 메소드란 무엇이고, 추상 클래스는 어떤 경우 사용될까?
먼저, 추상 메소드는 선언만 되어 있고 구현되어 있지 않은 메소드를 말한다. 그렇기 때문에 추상 클래스는 인스턴스화가 불가능하다.
abstract class Champion {
public abstract void chooseOne();
}
chooseOne()로 선언되어 있는 메소드는 추상 메소드이다. 그리고 추상 메소드를 포함하고 있는 클래스의 이름은 Champion이다.
여기에 String 멤버 변수와 일반 메소드start()를 추가했다.
abstract class Champion {
String spell = "";
public abstract void chooseOne();
public void start() {
chooseOne();
System.out.println("Spell : " + spell);
}
}
예제 소스는 게임 리그오브레전드를 생각하며 만들어봤다.
게임 리그오브레전드는 플레이할 챔피언과 스펠이라는 옵션을 선택해 플레이하게 된다. 이 게임에는 Top, Mid, Bottom이라는 포지션이 있고 해당 포지션에는 대표적인 챔피언이 존재한다.
게임에서 챔피언과 스펠을 선택하고 플레이(start()) 하는 것은 공통으로 정의된 룰이다. 다만 포지션마다 챔피언은 다르게 정해진다. 또한 스펠도 다르게 정할 수 있다. 즉 챔피온이라는 추상화된 개념은 각각의 세부적인 포지션으로부터 만들어졌다.
스펠의 출력 문법은 동일하다. 그래서 공통으로 사용되는 일반 메소드 start()에서 출력된다. 하지만 챔피온의 출력 문법은 각 포지션별 다른 출력 형태를 띈다. 따라서 추상 클래스를 상속받는 자식 클래스에서 구현하도록 했다.
추상 클래스 Champion를 상속받은 두 개의 클래스 Top, Mid, Bottom을 생성했다. extends를 통해 상속받은 클래스는 반드시 추상 메소드 chooseOne()를 오버라이딩해야 한다.
class Top extends Champion {
public void chooseOne() {
this.spell = "점멸";
System.out.println("Top : 티모");
}
}
class Mid extends Champion {
public void chooseOne() {
this.spell = "유체화";
System.out.println("Mid : 빅토르");
}
}
class Bottom extends Champion {
public void chooseOne() {
this.spell = "회복";
System.out.println("Bottom : 베인");
}
}
포지션에 따라 챔피온과 스펠을 선택했다.
이제 자식 클래스를 인스턴스화하고 일반 메소드를 실행해보자.
public class LgOfLegendDemo {
public static void main(String[] args) {
Top top = new Top();
top.start();
Mid mid = new Mid();
mid.start();
Bottom bottom = new Bottom();
bottom.start();
}
}
여기까지 확인된 추상 클래스 특징은 다음과 같다.
기본 뼈대가 만들어짐 : 추상 메소드를 자식 클래스에게 구현하게 함으로써 Top, Mid, Bottom뿐 아니라 추가적인 포지션이 생겨도 구현만 해주면 되는 기본적인 뼈대가 생성되었다.(기존 소스의 수정 없이 확장에 유연해짐)
개발 규칙 정의 : 상속을 통해 스펠 값과 챔피온을 선택하는 메소드명을 통일함으로써 규칙이 잡혔다.
코드의 중복 방지 : 공통으로 사용하는 start()를 한 곳에서 관리함으로써 코드 중복을 방지할 수 있다.
자유로운 구현 : Top, Mid, Bottom 클래스별 다르게 구현이 가능하다. (각기 다른 챔피언과 스펠을 선택)
정리해보자.
추상 클래스는 상속과 오버라이딩을 통해 자식 클래스에 구현을 강제할 때 사용된다. 강제를 하는 이유는 추상 클래스를 상속받는 자식 클래스가 추상 메소드를 구현함으로써 다형성을 실현하고 유지보수를 높이기 위함이다. 이것은 객체지향 설계의 중요한 첫 걸음이 될 것이다.
그럼에도 불구하고 인터페이스를 사용하는 이유는 클래스의 다중 상속이 안되기 때문이다.
본 예제에서 사용된 champ()라는 추상 메소드는 다양한 형태로 만들어졌다. Top, Mid, Bottom으로 한정하여 예제를 만들었지만 만약 새로운 포지션이 추가된다면 어떨까? 마찬가지로 추상 클래스 상속 후 champ() 메소드를 구현해서 사용하면 될 것이다.(확장)
또한 추상 클래스를 사용함으로써 통일된 코드로 규격화가 가능하다. 때문에 팀 프로젝트를 진행할 때, 소스의 중복 방지와 개발 퍼포먼스가 한층 더 나아질 것이다.
인터페이스
인터페이스는 추상 메소드와 상수로 이루어져있다. 추상 메소드(abstract)와 상수(public static final)는 컴파일시 자동 인식을 해주기 때문에 키워드를 생략해도 된다.