이번 편의 목표: abstract 클래스와 abstract 메서드를 만들고, 자식이 구현하기 전에는 컴파일되지 않는다는 점을 확인합니다.

Book은 실제로 대출할 수 있는 종이책입니다. new Book(...)이 자연스럽습니다. 반대로 자료 전체처럼 혼자서는 대출 단위가 아닌 공통 틀이 필요할 때가 있습니다. 그때 클래스를 abstract로 선언하면 그 자체는 객체를 만들 수 없습니다.

앞서 만든 Book과 EBook을 지울 필요는 없습니다. 추상 클래스는 그 둘을 부정하는 문법이 아니라, 인스턴스가 되면 안 되는 부모를 만들 때 쓰는 도구입니다.

구현이 비어 있는 메서드

파일 이름은 LibraryItem.java입니다.

Java
public abstract class LibraryItem {
    private final String title;

    protected LibraryItem(String title) {
        if (title == null || title.isBlank()) {
            throw new IllegalArgumentException("제목이 필요합니다.");
        }
        this.title = title.trim();
    }

    public String getTitle() {
        return title;
    }

    public abstract int lateFee(int lateDays);
}

abstract 메서드는 본문이 없습니다. 세미콜론으로 끝납니다. 이 메서드를 하나라도 가진 클래스는 abstract 클래스여야 합니다. new LibraryItem("개미")는 컴파일 오류입니다. 연체료 규칙이 아직 없기 때문입니다.

protected 생성자는 자식 클래스가 super로 호출할 수 있게 둡니다. public이 아니므로 외부에서 아무 조건 없이 생성하는 통로를 줄입니다. 어차피 추상 클래스는 직접 생성할 수 없습니다.

자식이 메서드를 구현

종이 자료와 잡지는 연체료가 다릅니다. 각각 LibraryItem을 상속하고 abstract 메서드를 구현합니다.

Java
public class PrintedBook extends LibraryItem {
    public PrintedBook(String title) {
        super(title);
    }

    @Override
    public int lateFee(int lateDays) {
        return lateDays * 100;
    }
}
Java
public class Magazine extends LibraryItem {
    public Magazine(String title) {
        super(title);
    }

    @Override
    public int lateFee(int lateDays) {
        return lateDays * 200;
    }
}

자식이 lateFee를 구현하지 않으면 그 자식도 abstract가 되어야 합니다. 보통의 구체 클래스는 부모의 추상 메서드를 모두 구현합니다.

Java
LibraryItem[] items = {
    new PrintedBook("개미"),
    new Magazine("월간 과학")
};
System.out.println(items[0].lateFee(3));
System.out.println(items[1].lateFee(3));

출력은 300과 600입니다. 변수 타입은 LibraryItem이지만 호출은 실제 객체인 PrintedBook과 Magazine의 구현을 탑니다. 13편의 다형성과 같은 원리이고, 부모 쪽 메서드 본문만 없을 뿐입니다.

직접 해 보기

LibraryItem을 상속하는 클래스를 하나 더 만들고, 연체료를 하루 50원으로 구현하세요. 2일을 넘겨 100이 출력되면 됩니다.

해설

Java
public class DigitalIssue extends LibraryItem {
    public DigitalIssue(String title) {
        super(title);
    }

    @Override
    public int lateFee(int lateDays) {
        return lateDays * 50;
    }
}

DigitalIssue issue = new DigitalIssue("전자 잡지"); 를 만든 뒤 issue.lateFee(2)를 출력하면 100입니다.