Link
Today
Total
05-20 09:23
Archives
관리 메뉴

초보개발자 긍.응.성

(이펙티브 자바 3) 21. 인터페이스는 구현하는 쪽을 생각해 설계하라 본문

책 정리/이펙티브 자바 3

(이펙티브 자바 3) 21. 인터페이스는 구현하는 쪽을 생각해 설계하라

긍.응.성 2020. 10. 20. 01:25
반응형

인터페이스 설계에 주의할 점에 대하여 짚고 넘어가자

디폴트 메서드

자바 8 이전에는 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 방법이 없었다. 자바 8에 와서 디폴트 메서드를 통해 기존 인터페이스에 메서드를 추가할 수 있게 되었지만, 이로써 발생할 수 있는 문제들 또한 존재한다.

디폴트 메서드를 선언하면, 그 인터페이스를 구현한 후 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 되어있다. 디폴트 메서드는 구현 클래스에 대해 아무것도 모른채 무작정 '삽입'될 뿐이다.

그리고 이러한 디폴트 메서드의 삽입이 불변식을 깨트릴 수 있다.

디폴트 메서드의 추가로 생길 수 있는 문제

자바 8의 Collection 인터페이스에 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;
}

인자로 Predicate를 받아 true를 반환하는 모든 원소를 제거한다.

아파치에서 제공하는 org.apache.commons.collections4.collection.SynchronizedCollection은 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공한다. 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스이다.

아파치의 SynchronizedCollection은 removeIf를 재정의하고 있지 않다. 이 클래스를 자바 8과 함께 사용한다면 디폴트 메서드인 removeIf의 디폴트 구현을 물려받게 되며, 모든 메서드에 대한 호출을 알아서 동기화해주지 못한다. removeIf의 구현은 동기화나 락 객체에 대해 아무것도 모르고 있기 때문이다.

따라서 SynchronizedCollection 인스턴스를 여러 스레드가 공유하는 환경에서 한 스레드가 removeIf를 호출하면 ConcurrentModificationException이 발생한다. 디폴트 메서드는 컴파일에 성공한다고 해도 기존 구현체에 런타임 오류를 일으킬 수 있는 것이다.

디폴트 메서드를 추가해야할 때

기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우가 아니면 피해야만 한다. 추가할 때 기존 구현체들과 충돌하지 않을지도 심사숙고해야 한다.

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

그렇다고 인터페이스 설계 시 아무 고민 없이 디폴트 메서드를 추가하라는 것은 아니다. 인터페이스 설계에는 여전히 세심한 주의가 필요하며 릴리즈 이전에 반드시 꼼꼼한 테스트를 거쳐야 만 한다.

정리

자바 8 이후, 디폴트 메서드를 통해 인터페이스에 메서드를 추가할 수 있게 되었다. 하지만 디폴트 메서드의 추가로써 발생할 수 있는 문제를 미리 알고, 꼭 필요한 경우에만 추가하는 것을 권장한다. 기존의 인터페이스보다 새로운 인터페이스에 디폴트 메서드를 추가하는 것이 상대적으로 훨씬 안전하고 좋은 선택이다. 하지만, 인터페이스 설계는 항상 세심한 주의가 필요하며 많은 테스트를 통해 릴리스할 수 있도록 하자.

 

반응형
Comments