[아이템 21] 인터페이스는 구현하는 쪽을 생각해 설계하라

Jimin Lim·2022년 5월 16일
0

Effective Java

목록 보기
21/38
post-thumbnail

아이템 21

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

자바 8 이전에는 인터페이스에 메서드를 추가할 방법이 없었다. 인터페이스에 메서드를 추가한다면 그것을 구현한 클래스에도 그 메서드가 존재해야 하는데, 이미 존재할 확률은 매우 낮기 때문이다.
디폴트 메서드를 선언한다면, 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 되며 생각할 수 있는 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하는 것은 어렵다.

removeIf 메서드의 예

다음은 자바 8의 Collection 인터페이스에 추가된 removeIf 메서드의 예시이다.

	//true를 반환하면 remove 호출해 원소 제거
	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 구현체와 잘 어우러지는 것은 아니다. 대표적으로 SynchronizedCollection이 존재한다.

SynchronizedCollection 클래스는 클라이언트가 제공한 객체로 락을 거는 기능을 추가로 제공한다. 즉, 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스다.
현재 SynchronizedCollection 클래스는 removeIf 메서드를 재정의하고 있지 않아, 자바 8과 사용한다면 디폴트 구현을 물려받게 되어 락 객체를 사용할 수 없게 된다.

자바 플랫폼 라이브러리에서도 이런 문제를 예방하기 위해 일련의 조치를 취했다.

  1. 구현한 인터페이스의 디폴트 메서드를 재정의하고
  2. 다른 메서드에서는 디폴트 메서드를 호출하기 전에 필요한 작업을 수행하도록 했다.

SynchronizedCollection이 반환하는 package-private 클래스들은 removeIf를 재정의하고, 이를 호출하는 다른 메서드들은 호출하기 전, 동기화를 하도록 했다.

인터페이스 설계시 유의점

(1) 디폴트 메서드는 기존 구현체에 런타임 오류를 일으킬 수 있다.

기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요하지 않다면 피해야 한다. 또한 추가하고자 하는 디폴트 메서드가 기존 구현체들과 충돌하지 않을지도 고민해봐야 한다.

새로 만드는 인터페이스에서 표준적인 메서드 구현을 제공하는 데는 유용하지만 존재하던 인터페이스에서 제거, 수정하는 용도가 아님을 명심해야 한다.

(2) 인터페이스를 설계할 때는 여전히 세심한 주의를 기울여야 한다.

디폴트 메서드로 기존 인터페이스에 새로운 메서드를 추가하면 커다란 위험도 딸려 온다.

새로운 인터페이스라면 릴리스 전 반드시 테스트를 거쳐야 한다. 각 인터페이스의 인스턴스를 다양한 작업에 활용하는 클라이언트도 여러 개 만들어 봐야 하며, 서로 다른 방식으로 최소한 세 가지는 구현해 봐야 한다.

profile
💻 ☕️ 🏝 🍑 🍹 🏊‍♀️

0개의 댓글