반응형

ITEM 21 "인터페이스는 구현하는 쪽을 생각해 설계하라"

 

인터페이스를 한 번 설계하고 릴리즈하면 다시 수정하기가 매우 어렵다.

메서드를 추가하고 싶어도 그 인터페이스의 구현체들을 모두 찾아 직접 수정해야 되기 때문이다. 자바 8 이후에는 이러한 불편을 해소하고자 인터페이스에 메서드 구현을 추가할 수 있도록 디폴트 메서드를 지원하기 시작했다.

 

디폴트 메서드를 선언하면, 그 인터페이스를 구현한 모든 클래스에서 재정의하지 않아도 사용할 수 있다. 기존 인터페이스에 메서드를 추가하는게 쉬어졌지만, 사실 구현체들은 인터페이스에서 새로운 메서드가 추가되었는지 알 수 없다. 그러니 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하기가 어렵긴 마찬가지다.

 

 

자바 8 부터 Collection 인터페이스에 추가된 디폴트 메서드인 removeIf() 를 살펴보자. 이 메서드는 인자로 주어진 Boolean 함수(predicate)가 true 를 반환하는 모든 원소를 제거한다. 그런데 이 디폴트 메서드가 현존하는 모든 Collection 구현체와 잘 어우러지는 것은 아니다.

 

아파치 커먼즈 라이브러리의 org.apache.commons.collections4.collection.SynchronizedCollection 클래스는 java.util의 Collections.synchronizedCollection 정적 팩토리 메서드가 반환하는 클래스와 비슷하다. 아파치 버전은 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공합니다. 즉, 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스이다.

 

아파치의 SynchronizedCollection 클래스는 처음에 removeIf 메서드를 재정의하지 않고 있었다.

(현재는 지원되고 있음. https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/collection/SynchronizedCollection.html)

 

만약 재정의되지 않았던 옛날 버전의 클래스를 자바 8과 함께 사용한다면(removeIf 의 디폴트 구현을 물려받게 된다면), removeIf 의 구현이 동기화에 관해 아무것도 모르기 때문에 락 객체를 사용할 수 없게 된다. 따라서 SynchronizedCollection 인스턴스를 여러 쓰레드가 공유하는 환경에서 한 쓰레드가 removeIf를 호출하면 ConcurrentModificationException이 발생하거나 다른 예기치 못한 결과로 이어질 수 있다.

 

그래서 기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우가 아니라면 피하자.

새로운 인터페이스를 만드는 경우라면, 표준적인 메서드 구현을 제공하는데 유용한 수단이 될 수 있다. 이전 아이템에서 설명했듯이 해당 인터페이스를 활용하는 클라이언트도 여러 개 만들어서 의도한 용도에 맞게 잘 부합되는지 확인하자.

반응형

+ Recent posts