추상 클래스와 인터페이스
공통 구현과 반드시 제공해야 할 동작을 분리할 때 추상 클래스와 인터페이스를 사용할 수 있습니다.
추상 클래스는 인스턴스 상태와 생성자, 일부 구현을 함께 가질 수 있습니다.
인터페이스는 여러 타입이 공유하는 역할 계약을 표현하며 클래스 하나가 여러 인터페이스를 구현할 수 있습니다.
둘을 문법 기능 목록으로 비교하기보다 상태 소유와 역할 조합이 필요한지 판단해야 합니다.
이 절에서는 확장 축을 작게 유지하는 방법을 살펴봅니다.
추상 클래스는 공통 상태와 골격을 가진다
직접 객체를 만들 수 없지만 필드, 생성자, 구체 메서드와 추상 메서드를 함께 선언합니다.
밀접한 하위 타입이 같은 불변식을 공유할 때 적합합니다.
인터페이스는 구현 가능한 역할을 선언한다
서로 다른 클래스 계층에서도 같은 기능을 제공할 수 있습니다.
호출자는 구체 클래스 대신 작은 인터페이스에 의존합니다.
예제의 추상 클래스는 공통 상태와 실행 골격을 갖고, 인터페이스는 서로 다른 객체가 제공할 역할을 선언합니다. 두 도구를 경쟁 문법으로 보지 말고 무엇을 공유하려는지에 따라 나누어 읽습니다.
public class AbstractRoles {
interface Named {
String name();
default String displayName() { return name().toUpperCase(); }
}
static abstract class Shape implements Named {
private final String name;
Shape(String name) { this.name = name; }
@Override public String name() { return name; }
abstract double area();
final String summary() { return displayName() + ":" + area(); }
}
static final class Circle extends Shape {
private final double radius;
Circle(double radius) { super("circle"); this.radius = radius; }
@Override double area() { return Math.PI * radius * radius; }
}
public static void main(String[] args) {
Shape shape = new Circle(2);
System.out.printf("%s%n", shape.summary());
// new Shape("x"); // 컴파일 오류: 추상 클래스 직접 생성 불가
}
}Shape 계층과 관계없는 클래스도 Named 인터페이스만 따로 구현할 수 있습니다. 간단한 User implements Named를 추가해 displayName()을 호출해 보면, 공통 상태를 상속하지 않고 행동 계약만 공유하는 장점을 확인할 수 있습니다.
CIRCLE:12.566370614359172Shape가 공통 상태와 전체 summary 흐름을 지키고 Circle은 면적 계산만 제공합니다.
추상 클래스의 템플릿 메서드는 전체 순서를 고정하고 꼭 달라져야 할 계산 단계만 추상 메서드로 엽니다. 하위 클래스가 공통 검증 전후 순서를 바꾸면 안 될 때 유용한 형태입니다.
인터페이스는 이와 달리 상태 초기화 순서를 공유하지 않습니다. 서로 관계없는 타입에 작은 역할을 붙여 같은 호출자가 다룰 수 있게 하는 것이 주목적입니다.
한 클래스는 여러 역할을 조합할 수 있다
클래스 상속은 하나지만 인터페이스는 여러 개 구현할 수 있습니다.
거대한 인터페이스 하나보다 호출자가 실제로 쓰는 작은 역할로 나눕니다.
한 클래스가 여러 인터페이스를 구현할 수 있다는 것은 여러 역할을 동시에 약속할 수 있다는 뜻입니다. 다만 이름과 시그니처가 같은 default 메서드가 충돌하면 클래스에서 구현을 직접 골라 모호함을 해결해야 합니다.
interface Storable { void save(); }
interface Auditable { void audit(); }class Report implements Storable, Auditable {
public void save() { }
public void audit() { }
}// 클래스 상태와 생성 골격은 abstract class,
// 서로 다른 타입에 붙는 역할은 interface로 분리한다.템플릿 메서드는 흐름과 변형 지점을 나눈다
추상 클래스의 final 메서드가 전체 순서를 정하고 일부 단계만 추상 메서드로 열 수 있습니다.
하위 타입이 불변식 순서를 바꾸지 못하게 합니다.
default 메서드는 인터페이스 진화를 돕는다
인터페이스에 공통 동작을 추가할 때 기존 구현을 깨뜨리지 않을 수 있지만 상태를 소유하는 용도로 쓰지 않습니다.
충돌하는 default 구현은 클래스에서 명시적으로 해결합니다.
default 메서드는 기존 구현 클래스를 한꺼번에 깨뜨리지 않고 작은 공통 동작을 추가할 때 유용합니다. 인터페이스에 상태가 필요한 복잡한 구현까지 밀어 넣기보다, 구현체나 별도 협력 객체가 맡을 책임인지 함께 판단합니다.
공통 상태와 생성 과정이 핵심이면 추상 클래스, 여러 타입에 붙는 작은 역할이면 인터페이스를 우선 검토합니다.