ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 20. 추상 클래스보다는 인터페이스를 우선하라
    JAVA/Effective java 2021. 3. 3. 12:22
    • 자바 8부터 인터페이스도 디폴트 메서드를 제공할 수 있게 되어, 이제는 추상 클래스와 인터페이스 모두 인스턴스 메서드를 구현 형태로 제공할 수 있다.
    • 둘의 가장 큰 차이는 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다는 것이다.

     

    • 자바는 단일 상속만 지원하니, 추상 클래스 방식은 새로운 타입을 정의하는 데 커다란 제약을 안게 된다.
    • 반면 인터페이스가 선언한 메서드를 모두 정의하고 그 일반 규약을 잘 지킨 클래스라면 어떤 클래스를 상속했든 같은 타입으로 취급된다.

     

    • 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다. 
    • 인터페이스가 요구하는 메서드를 추가하고, 클래스 선언에 implemenets 구문만 추가하면 끝인데,
    • 자바 플랫폼에서도 Comparable, Iterable, AutoCloseable 인터페이스가 새로 추가됐을 때 표준 라이브러리의 수많은 기존 클래스가 이 인터페이스들을 구현한 채 릴리스 됐다.

     

    • 반면 기존 클래스 위에 새로운 추상 클래스를 끼워넣기는 어려운 게 일반적이다.
    • 두 클래스가 같은 추상 클래스를 확장하길 원한다면, 그 추상 클래스는 계층구조상 두 클래스의 공통 조상이어야 하고 이 방식은 클래스 계층 구조에 혼란을 일으킨다.

     

     

     

     

     

     

    인터페이스는 믹스인 정의에 안성맞춤이다.


    • 예를 들어 Comparable은 자신을 구현한 클래스의 인스턴스들끼리는 순서를 정할 수 있다고 선언하는 믹스인 인터페이스이다.
    • 대상 타입의 주된 기능에 선택적 기능을 혼합한다고 해서 믹스인이라 부르는데 추상 클래스로는 기존 클래스에 덧씌울 수 없기 때문에 믹스인을 정의할 수 없다.

     

     

     

     

     

     

    인터페이스로는 계층구조가 없는 타입 프레임워크를 만들 수 있다.


     

    • 가수 인터페이스와 작곡가 인터페이스가 있다고 해보자.
    • 주변에는 작곡도 하는 가수가 제법 있는데 이 코드처럼 타입을 인터페이스로 정의하면 가수 클래스가 Singer와 Songwriter 모두를 구현해도 전혀 문제되지 않는다.
    • 또한 Singer와 SongWriter 모두를 확장하고 새로운 메서드까지 추가한 제3의 인터페이스를 정의할 수도 있다.

     

    • 인터페이스의 메서드 중 구현 방법이 명백한 것이 있다면, 그 구현을 디폴트 메서드로 제공해 일감을 덜어줄 수 있다.
    • 디폴트 메서드를 제공할 때는 상속하려는 사람을 위한 설명을 @implSpec 자바독 태그를 붙여 문서화해야 한다.

     

     

     

     

     

     

     

    인터페이스와 추상 골격 구현 클래스를 함께 제공하는 식으로 인터페이스와 추상 클래스의 장점을 모두 취하는 방법도 있다.


    • 인터페이스로는 타입을 정의하고, 필요하면 디폴트 메서드 몇 개도 함께 제공한다.
    • 그리고 골격 구현 클래스는 나머지 메서드들까지 구현한다. 이렇게 해두면 단순히 골격 구현을 확장하는 것만으로 이 인터페이스를 구현하는 데  필요한 일일 대부분 완료된다. 바로 템플릿 메서드 패턴이다
    • 관례상 인터페이스 이름이 Interface라면 그 골격 구현 클래스의 이름은 AbstractInterface로 짓는다.
    • 좋은 예로, 컬렉션 프레임워크의 AbstractCollection, AbstractSet, AbstractList, AbstractMap 각각이 핵심 컬렉션 인터페이스의 골격 구현이다.

    Collection 인터페이스와 추상 골격 구현 클래스인d AbstractCollection

     

     

     

     

     

    • 골격 구현은 그 인터페이스로 나름의 구현을 만들려는 프로그래머의 일을 상당히 덜어준다.
    • 다음의 코드는 완벽히 동작하는 List 구현체를 반환하는 정적 팩터리 메서드로, AbstractList 골격 구현으로 활용했다.

    • 이 예는 int 배열을 받아 Integer 인스턴스의 리스트 형태로 보여주는 어댑터이다.
    • 다음의 코드는 완벽히 동작하는 List 구현체를 반환하는 정적 팩터리 메서드로, AbstractList 골격 구현으로 활용했다.

     

     

    • 골격 구현 클래스는 추상 클래스처럼 구현을 도와주는 동시에, 추상 클래스로 타입을 정의할 때 따라오는 심각한 제약에서는 자유롭다는 점에 있다.
    • 골격 구현을 확장하는 것으로 인터페이스 구현이 거의 끝나지만, 꼭 이렇게 해야 하는 것은 아니다.
    • 구조상 골격 구현을 확장하지 못하는 처지라면 인터페이스를 직접 구현해야 한다.
    • 이런 경우라도 인터페이스가 직접 제공하는 디폴트 메서드의 이점을 여전히 누릴 수 있다.

     

     

     

     

     

     

     

    골격 구현 작성


     

    • 인터페이스를 잘 살펴 다른 메서드들의 구현에 사용되는 기반 메서드들을 선정한다.
    • 이 기반 메서드들은 골격 구현에서 추상 메서드가 될 것이다.
    • 기반 메서드들을 사용해 직접 구현할 수 있는 메서드를 모두 디폴트 메서드로 제공한다.
    • 기반 메서드, 디폴트 메서드로 만들지 못한 메서드가 남아 있다면, 이 인터페이스를 구현하는 골격 구현 클래스를 만들어 남은 메서드들을 작성해 넣는다.

     

    • 간단한 예로 Map.Entry 인터페이스를 살펴보자.

    • Map.Entry 인터페이스는 getKey, getValue 를 기반 메서드로 하며 선택적으로 setValue도 포함할 수 있다. 또한 equals와 hashCode 동작 방식도 정의해놨다.
    • Object 메서드들은 디폴트 메서드로 제공해서는 안되므로, 해당 메서드들은 모두 골격 구현 클래스에 구현한다. toString도 기반 메서드를 사용해 구현해놓았다.
    • Map.Entry 인터페이스나 그 하위 인터페이스로는 이 골격 구현을 제공할 수 없다. 디폴트 메서드는 equals, hashCode, toString 같은 Object 메서드를 재정의할 수 없기 때문이다.

     

     

     

     

     

    핵심 정리


    • 일반적으로 다중 구현용 타입으로는 인터페이스가 가장 적합하다.
    • 복잡한 인터페이스라면 구현하는 수고를 덜어주는 골격 구현을 함께 제공하는 방법을 고려하자.
    • 골격 구현은 가능한 한 인터페이스의 디폴트 메서드로 제공하여 그 인터페이스를 구현한 모든 곳에서 활용하도록 하는 것이 좋다.
    • '가능한 한' 이라고 한 이유는, 인터페이스에 걸려 있는 구현상의 제약으로 골격 구현을 추상 클래스로 제공하는 경우가 더 흔하기 때문이다.

     

     

     

     

     

    참고 자료 


    이펙티브 자바

Designed by Tistory.