책/Effective Java

객체는 인터페이스를 사용해 참조하라

728x90
반응형

cf) Item.51 : 매개변수 타입으로 클래스가 아닌, 인터페이스를 사용하라 

→ 객체는 클래스가 아닌 인터페이스로 참조하라로 확장 

 

적합한 인터페이스만있다면 매개변수뿐 아니라, return, 변수, 필드를 전부 인터페이스 타입으로 선언하자

  • 실제 클래스를 사용해야 할 상황은 오직 생성자로 생성할 때뿐이다.

 

인터페이스를 타입으로 사용하는 습관은 프로그램을 훨씬 유연하게 만들어 준다

 

// Good
Set<Son> sonSet = new LinkedHashSet<>();

Set<Son> sonSet = new HashSet<>(); //유연하게 변경가능

// Bad
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
  • 인터페이스를 타입으로 사용하면 이후 구현 클래스를 교체할 때 새 클래스의 생성자(혹은 다른 정적 팩토리)를 호출만 하면 된다.
    • 다른 코드는 손대지 않고 교체가 가능하다 

 

인터페이스를 타입으로 사용할 때 주의할 점

  • 인터페이스의 일반 규약 외의 특별한 기능에 의존하고 있다면, 새로운 클래스도 반드시 동일한 기능을 제공해야한다. 
    • ex) LinkedHashSet의 순서 정책을 가정하고 동작하는 상황 : 
      • HashSet으로 바뀌면 반복자의 순회 순서를 보장하지 않음 

구현 타입을 바꾸는 상황

기존의 코드보다 성능을 높이거나 새로운 기능을 제공할 수 있을 때 

 

Map<Member> memberMap = new HashMap<>();

// EnumMap 변경을 통해 속도 증가 및 순회 순서를 키의 순서로 보장 
// 단, EnumMap은 키가 열거 타입일 때만 사용 가능 
Map<Member> memberMap = new EnumMap<>();

//Member가 열거 타입이 아니라면? 
//LinkedHashMap을 통해 비슷한 성능을 끌어올리며 순회 순서를 예측 가능하게 보장 
Map<Member> memberMap = new LinkedHashMap<>();

 

Q.  선언 타입과 구현 타입을 동시에 바꿀 수 있으니, 변수를 구현 타입으로 선언해도 괜찮지 않나요??

  • 클라이언트에서 기존 타입에서만 제공하는 메서드를 사용하거나, 기존 타입을 사용해야만 하는 다른 메서드에 해당 인스턴스를 넘겼다면 컴파일러가 되지 않는 문제가 발생한다. 
  • 따라서 변경 최소화, 에러 발생 가능성 줄이기 등을 고려하여 변수를 인터페이스 타입으로 선언하면 이런 일이 발생하지 않는다. 

 

클래스 참조를 해야할 경우

- 적합한 인터페이스가 없다면 당연히 클래스 참조를 해야 한다.

  1. 값 클래스 
    • String, BigInteger와 같은 값 클래스는 여러가지로 구현할 수 있다고 생각하고 설계하는 일은 거의 없다.
    • 따라서 final인 경우가 많고 인터페이스가 별도로 존재하는 경우도 드물다. 
    • 이런 값 클래스는 매개변수, 변수, 필드, 반환 타입으로 사용해도 무방하다. 
  2. 클래스 기반으로 작성된 프레임워크 객체 
    • ex) OutputStream 등 java.io 패키지의 여러 클래스가 해당 부류에 속한다. 
    • 프레임워크가 제공하는 클래스 기반 객체라도 특정 구현 클래스보다는 기반(추상) 클래스를 참조하자 
  3. 인터페이스에는 없는 특별한 기능을 제공하는 클래스 (남발해선 X) 
    • 특별한 기능이 꼭 필요한 경우 최소한의 범위 내에서 클래스 참조를 사용해도 된다.
    • ex) PriorityQueue 클래스는 Queue 인터페이스에는 없는 comparator 메서드를 제공하는데 이런 추가 메서드가 필요한 경우에만 사용하자 

 

정리 
- 클래스보다는 인터페이스, 추상 클래스를 참조하도록하자 
- 적합한 인터페이스가 없다면 클래스의 계층구조 중 필요 기능을 만족하는 가장 덜 구체적인(상위의) 클래스를 타입으로 사용하자 

 

 

728x90
반응형