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

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

  • (개념) 객체를 담는 변수, 필드, 메서드 매개변수, 반환 타입 등을 선언할 때, 실제 객체의 구체 클래스 타입 대신 해당 객체가 구현한 인터페이스 타입을 사용하라는 원칙.

  • (확장) 아이템 51 "매개변수 타입으로 인터페이스 사용" 원칙을 프로그램 전반으로 확장.


핵심 원칙

  • 참조는 인터페이스로 (Reference via Interface)

  • 생성만 클래스로 (Instantiate with Class)

    // 좋은 예: 인터페이스로 참조, 클래스로 생성
    List<User> users = new ArrayList<>();
    Map<String, Data> dataMap = new HashMap<>();
    MemberService service = new MemberServiceImpl();
    
    // 나쁜 예: 구체 클래스로 참조 (유연성 저하)
    ArrayList<User> users2 = new ArrayList<>();

장점

graph LR
  A[Client Code] --> B(Service Interface);
  B -- impl --> C[ServiceImpl_A];
  B -- impl --> D[ServiceImpl_B];
  B -- impl --> E[ServiceImpl_C];
  • 유연성 향상 (Increased Flexibility)

  • 구현 교체 용이 (Easy Implementation Swapping)

  • 테스트 용이성 & DI 활용 (Improved Testability & DI Usage)

    @Component // 또는 @Controller, @Service 등
    public class MyComponent {
        private final MemberService memberService; // ★ 인터페이스 타입으로 주입받음
    
        @Autowired // 생성자 주입 권장
        public MyComponent(MemberService memberService) {
            this.memberService = memberService;
        }
    
        public void doSomething() {
            // memberService의 실제 구현이 MemberServiceImpl이든,
            // NewMemberServiceImpl이든 이 코드는 변경 불필요
            memberService.someMethod();
        }
    }

주의점 (Potential Issues)

  • 암묵적 동작 의존 (Dependency on Implicit Behavior)

    • LinkedHashSet -> HashSet

  • 컴파일 시점 오류 (Compile-time Errors)

    ArrayList<String> names = new ArrayList<String>();
    //List<String> names = new LinkedList<>();
    
    names.trimToSize(); // 컴파일 에러! List 인터페이스 및 LinkedList에는 해당 메서드 없음

예외

  • 적합한 인터페이스 부재 (No Suitable Interface)

  • 값 클래스 (Value Classes)

    • String, Integer, BigInteger 등. 클래스 타입 직접 사용

  • 클래스 기반 프레임워크 (Class-based Frameworks)

    • java.io.InputStream, OutputStream

    void processStream(InputStream is) throws IOException {
        int data;
        while ((data = is.read()) != -1) {
            // ... 데이터 처리 ...
        }
        is.close();
    }
    
    // 사용 예시
    InputStream fileInput = new FileInputStream("data.txt");
    processStream(fileInput);
    
    byte[] memoryData = ...;
    InputStream memoryInput = new ByteArrayInputStream(memoryData);
    processStream(memoryInput);
  • 구체 클래스 고유 메서드 필수 사용 시 (Need for Class-specific Methods)

    • ArrayList의 TrimToSize()

    • PriorityQueuecomparator()


예외 상황 대처 (Handling Exceptions)

  • Trade-off 인지 (Acknowledge Trade-off) (유연성 < 특정 기능 이점)

  • 대처 방안 (Choose Your Approach)

    1. 구체 클래스 타입 직접 선언 (Declare Concrete Class Type)

    2. instanceof + 캐스팅 (Use instanceof + Casting)

    if (users instanceof ArrayList) {
        ((ArrayList<User>) users).ensureCapacity(1000);
        // ArrayList 고유 메서드 사용
    }

최종 요약 (Key Takeaway)

"특별한 이유(고유 메서드 필수 사용 등)가 없다면, 항상 가장 추상적인 타입(인터페이스 > 상위 클래스)으로 참조하라. 특정 구현의 이점이 유연성보다 명확히 중요할 때만 예외를 고려하라."

Last updated