Item 20. 추상 클래스보다는 인터페이스를 우선하라

심규환·2022년 1월 29일
0

Effective Java

목록 보기
19/29

이번에 볼 것은 추상 클래스와 인터페이스이다. 둘 모두 디폴트 메서드를 제공할 수 있게 되어 인스턴스 메서드를 구현 형태로 제공할 수 있다.
이 둘의 가장 큰 차이점으로는 추상 클래스는 상속 받은 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다. 반면 인터페이스를 구현한 클래스는 어떤 클래스를 상속했던 인터페이스와 같은 타입으로 취급된다. 여러 타입을 가질 수 있게 된다.

인터페이스의 장점으로는 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해넣을 수 있다는 것이다. 그저 뒤에 implements 구문만 추가하면 인터페이스의 타입과 메서드를 정의하여 사용할 수 있게 된다. 만약 여러 클래스에게 추상 클래스의 기능을 추가 하고 싶다면 상속을 해야 하는데. 그러면 상속 받은 클래스가 모두 같은 조상을 가지게 되어 계층 구조에 혼란을 주게 된다.

인터페이스는 믹스인 정의에 잘 맞는다. 믹스인 정의란 기본적으로 인터페이스가 주어지는 타입외에 추가적으로 선택적인 행위가 가능하게 해준다. 무슨 말인가 하면 예를 들어 Comparable을 구현한 클래스들은 추가적으로 인스턴스끼리 순서를 정할 수 있다고 선언할 수 있다. 추상 클래스로는 덧씌우는게 불가능하기 때문에 이처럼 하는 것이 불가능하다.

인터페이스 + 래퍼 클래스를 사용하면 인터페이스의 기능을 향상시키는 안전하고 강력한 수단이 된다. 그리고 인터페이스에서 구현 방법이 명백한 메서드가 있다면 디폴트 메서드로 제공한 뒤, @impleSpec 자바독 태그를 붙여서 사용법을 명시해야 한다.
디폴트 메서드 사용시의 제약이 있다. 절대 equals와 hashCode 같은 Object 메서드를 디폴트 메서드로 제공해서는 안된다. 이는 구현하려는 클래스에서 재정의할 수 있도록 해야 하낟. 또 인터페이스는 인스턴스 필드를 가질 수 없고 public이 아닌 정적 맴버도 가질 수 없으니 이를 주의하여 디폴트 메서드를 만들어야 한다.

다음은 인터페이스 + 추상 골격 구현 클래스를 설명하겠다. 이는 인터페이스와 추상 클래스의 장점을 모두 취하는 방법으로 템플릿 메서드 패턴이라 한다.
먼저 인터페이스에 타입을 정의하고 확실하고 필요한 디폴트 메서드를 정의한 뒤 이를 구현한 추상 골격 클래스에 나머지 메서드들을 구현하게 한다.
이제 필요할 때 이를 추상 골격 클래스를 추가해서 사용하면 된다.
아래는 AbstractList 추상 골격 구현의 예이다.

static List<Integer> intArrayAsList(int[] a){
    Objects.requireNonNull(a);
    
    // 추상 골격 구현 클래스
    return new AbstractList<>(){
    	@Override public Integer get(int i){
        	return a[i];
        }
        
        @Override public Integer set(int i, Integer val){
        	int oldVal = a[i];
            a[i] = val;
            return oldVal;
        }
        
        @Override public int size(){
        	return a.length;
        }
    };
}

이미 잘 구현되어 있는 추상 골격 구현 클래스를 사용해서 필요한 부분만 재정의해서 List를 반환하게 한다. 같은 인터페이스 조상을 가지기 때문에 무리없이 반환이 가능하다. 하지만 위의 코드는 int <-> Integer 박싱, 언박싱이 이루어지기 때문에 성능으로는 좋지 않다.

골격 구현 클래스의 장점은 추상 클래스처럼 구현에 도움을 주면서 추상 클래스로 타입을 정의할 때의 제약에서 벗어나게 해준다는 것이다. 골격 구현 클래스를 사용하는 것이기 때문에 상속받은 것이 아니라 자유롭다. 만약 이 구현 클래스가 부족하다면 인터페이스를 직접 구현하면 된다.
또 골격 구현 클래스를 우회적으로 이용할 수 있다. 인터페이스를 구현한 뒤, 골격 구현 클래스를 private 내부 클래스로 둔 뒤, 각 메서드 호출을 내부 클래스의 인스턴스에 전달하여 사용하는 것이다. 이를 다중 상속이라 한다.

profile
장생농씬가?

0개의 댓글