[이펙티브 자바] 아이템21 | 인터페이스는 구현하는 쪽을 고려해 설계해라

제롬·2022년 3월 10일
0

이펙티브자바

목록 보기
21/25

인터페이스에 메서드 추가

  • 자바 8 전에는 기존 구현체를 깨뜨리지 않고 인터페이스에 메서드를 추가할 방법은 존재하지 않았다. 자바 8부터 디폴트 메서드를 통해서 기존 인터페이스에 메서드를 추가할 수 있게 되었다.

  • 디폴트 메서드를 선언하면, 그 인터페이스를 구현한 후 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 된다.

  • 단, 이렇게 디폴트 메서드를 추가한다고해도 기존 구현체들과 매끄럽게 연동된다는 보장은 없다.

생각할 수 있는 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하기란 어렵기 때문이다.

[Çollection의 removeIf 디폴트 메서드]

default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean result = false;
        for (Iterator<E> it = iterator(); it.hasNext();){
            if(filter.test(it.next())){
                it.remove();
                result = true;
            }
        }
        return result;
}
  • 이 코드는 범용적으로 작성되었지만 현존하는 모든 Collection 구현체와 잘 어우러지는것은 아니다. 대표적인 예가 SyncronizedCollection이다.

  • 아파치 커먼즈 라이브러리의 이 클래스는 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공한다. 즉 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스다.

  • 현재는 SynchronizedCollection클래스가 removeIf메서드를 재정의하고 있지 않고있다. removeIf 메서드는 동기화에 관해 아무것도 알지 못하므로 락 객체를 사용할 수가 없다.

  • SynchronizedCollection 인스턴스를 여러 스레드가 공유하는 환경에서 한 스레드가 removeIf를 호출하면 ConcurrentModificationException이 발생하거나 다른 오류가 발생할 가능성이 있다.

디폴트 메서드 동기화 유지 방법

  • 자바에서는 디폴트 메서드가 일으킬 수 있는 문제 예방을 위해 취한 조취중 하나로 인터페이스의 디폴트 메서드를 재정의하고, 다른 메서드에서 디폴트 메서드를 호출하기 전에 필요한 작업을 수행하도록 했다.

[4.4 버전 이후로 동기적으로 구현]

	/**
    * @since 4.4
    */
    @Override
    public boolean removeIf(final Predicate<? super E> filter) {
        synchronized (lock) {
            return decorated().removeIf(filter);
        }
    }
  • 4.4 이전 버전에서는 동기적으로 구현되지 않아 removeIf 호출시 default 메서드를 호출하게되어 동기적으로 동작하지 못했다.

  • 4.4 이후 버전에서는 Collection.synchronizedCollection이 반환하는 package-private 클래스들은 removeIf를 재정의하고, 이를 호출하는 다른 메서드들은 디폴트 구현을 호출하기 전에 동기화 하도록 했다.

디폴트 메서드는 오류를 일으킬 가능성이 있다.

  • 디폴트 메서드는 컴파일에 성공하더라도 기존 구현체에 런타임 오류를 일으킬수 있다. 때문에 기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는일은 피해야한다.

  • 반면, 새로운 인터페이스를 만드는 경우라면 표준적인 메서드 구현을 제공하는데 아주 유용한 수단이며, 그 인터페이스를 더 쉽게 구현해 활용할 수 있게끔 해준다.

인터페이스 설계시 주의가 필요하다.

  • 디폴트 메서드라는 도구가 생겼지만 인터페이스를 설계할 때는 여전히 주의를 기울일 필요가 있다. 기존 인터페이스에 디폴트 메서를 추가할 시 어떤 위험이 딸려올지 알 수 없기 때문이다.

인터페이스 릴리즈전에 테스트를 거치자.

  • 새로운 인터페이스라면 릴리스 전에 반드시 테스트를 거쳐야한다. 서로 다른 방식으로 최소 세가지는 구현해보는것을 추천한다. 또한 다양한 클라이언트도 만들어 보는것이 좋다.

  • 이런 테스트 과정을 통해 결함을 찾아내야 한다. 인터페이스를 릴리스 한 후라도 결함을 수정하는게 가능할 경우도 있겠지만, 그 가능성에 기대서는 안된다.

0개의 댓글