아이템 21. 인터페이스는 구현하는 쪽을 생각해 설계하라. - ksw6169/effective-java GitHub Wiki

디폴트 메소드 도입 후의 문제점

  • 자바 8부터 인터페이스에 디폴트 메소드를 추가할 수 있는 기능이 도입되었으나, 기존 구현체들은 디폴트 메소드의 도입을 몰랐기 때문에 이런 변화에 발맞춰 수정될 기회가 없었다.
  • 따라서 자바 8과 자바 8에서 제공하는 인터페이스를 구현한 모든 기존 구현체들은 매끄럽게 연동되리라는 보장이 없다. (자바 플랫폼 라이브러리는 이에 대응해 개발되었으니 문제가 없을 수 있다.)
  • 예컨대 자바 8의 Collection 인터페이스에는 다음과 같은 디폴트 메소드가 제공되었다.
/**
 * 반복자를 이용해 순회하면서 주어진 Predicate에 각 원소가 인수로 주어졌을 때 
 * true를 반환하는 경우 그 원소는 제거된다. 
 */
default boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    boolean removed = false;
    final Iterator<E> each = iterator();
    while (each.hasNext()) {
        if (filter.test(each.next())) {
            each.remove();
            removed = true;
        }
    }
    return removed;
}
  • apache.commons.collections4의 SynchronizedCollection은 Collection 인터페이스를 구현한 기존 클래스로 클라이언트가 제공한 Collection 객체로 락을 거는 기능을 추가로 제공한다.
  • 즉, 모든 메소드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스다.
public class SynchronizedCollection<E> implements Collection<E>, Serializable {
    private final Collection<E> collection;
    protected final Object lock;

    public static <T> SynchronizedCollection<T> synchronizedCollection(Collection<T> coll) {
        return new SynchronizedCollection(coll);
    }

    public boolean add(E object) {
        synchronized(this.lock) {
            return this.decorated().add(object);
        }
    }

    ...
}
  • 문제는 SynchronizedCollection은 removeIf 를 재정의하고 있지 않다는 점이다. (이 책을 작성할 시점에 제공되지 않았지만 현재는 제공된다.)
  • 따라서 이 클래스를 자바 8과 같이 사용한다면 removeIf에 대해서는 동기화 기능이 제공되지 않는다. (즉, 자신이 한 약속을 지키지 못하게 된다.)

디폴트 메소드 작성 시 주의할 점

  • 디폴트 메소드는 컴파일에 성공하더라도 기존 구현체에 런타임 오류를 일으킬 수 있다.
  • 기존 인터페이스에 디폴트 메소드로 새 메소드를 추가하는 일은 꼭 필요한 경우가 아니면 피해야 한다. 만약 추가해야 한다면 기존 구현체들과 충돌하지는 않을지 심사숙고해야 한다.
  • 반면에 새로운 인터페이스를 만드는 경우라면 표준적인 메소드 구현을 제공하는 데 아주 유용한 수단이며, 그 인터페이스를 더 쉽게 구현해 활용할 수 있게끔 해준다.
  • 한편 디폴트 메소드는 인터페이스로부터 메소드를 제거하거나 기존 메소드의 시그니처를 수정하는 용도가 아님을 명심해야 한다. 이런 형태로 인터페이스를 변경하면 반드시 기존 클라이언트를 망가뜨리게 된다.
  • 정리하자면 디폴트 메소드라는 도구가 생겼더라도 인터페이스를 설계할 때는 여전히 세심한 주의를 기울여야 한다.

새로운 인터페이스라면 릴리즈 전에 반드시 테스트를 거쳐야 한다.

  • 개발자들은 나름의 방식으로 구현할 것이니 최소한 세 가지는 구현해봐야 한다.
  • 또한 각 인터페이스의 인스턴스를 다양한 작업에 활용하는 클라이언트도 여러 개 만들어봐야 한다.
  • 이러한 작업들을 거치면 인터페이스를 릴리즈하기 전에 결함을 찾아낼 수 있다. 인터페이스를 릴리즈한 후라도 결함을 수정하는 게 가능한 경우도 있겠지만, 절대 그 가능성에 기대서는 안된다.

참고 자료

  • Effective Java 3/E
⚠️ **GitHub.com Fallback** ⚠️