ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Item 20: Prefer interfaces to abstract classes
    독서/Effective Java 2021. 12. 19. 17:10

    추상 클래스보다는 인터페이스를 우선하라

     

    Java에는 다중 구현을 허용하는 유형을 정의하는 메커니즘은 인터페이스와 추상 클래스다.

    Java 8[JLS 9.4.3]에서 인터페이스에 대한 default 메소드가 도입된 이후로 두 메커니즘 모두 일부 인스턴스 메소드에 대한 구현을 제공할 수 있다.

     

    추상 클래스 vs 인터페이스

    추상 클래스에 의해 정의된 유형을 구현하려면 클래스가 추상 클래스의 하위 클래스여야 한다는 것이다.

    Java는 단일 상속만 허용하기 때문에 추상 클래스에 대한 이러한 제한은 유형 정의로서의 사용을 심각하게 제한한다.

    모든 필수 메서드를 정의하고 일반 규약을 준수하는 모든 클래스는 클래스 계층 구조에서 클래스가 있는 위치에 관계없이 인터페이스를 구현할 수 있다.

     

    인터페이스

    기존 클래스를 쉽게 개조하여 새 인터페이스를 구현할 수 있다. 필요한 메소드가 아직 존재하지 않는 경우 추가하고 클래스 선언에 implements 절을 추가만 하면 된다.

    Ex) 많은 기존 클래스가 플랫폼에 추가될 때 Comparable, Iterable 및 Autocloseable 인터페이스를 구현하도록 개조된다.

    기존 클래스는 일반적으로 새로운 추상 클래스를 추가할 수 없다. 두 클래스가 동일한 추상 클래스를 확장하도록 하려면 두 클래스의 조상이 되는 유형 계층 구조에서 상위 클래스를 배치해야 한다.

    이것은 적절한 여부에 관계없이 새로운 추상 클래스의 모든 자손이 강제로 유형 계층 구조에 큰 부수적 손상을 줄 수 있다.

     

    믹스인

    인터페이스는 "믹스인"을 정의하는 데 이상적이다. 느슨하게 말해서, mixin은 "기본 타입"에 추가하여 클래스가 일부 선택적 동작을 제공한다고 선언하기 위해 구현할 수 있는 타입이다.

    Ex) Comparable은 클래스가 해당 인스턴스가 다른 상호 비교 가능한 객체와 관련하여 정렬되었음을 선언할 수 있도록 하는 믹스인 인터페이스이다. 이러한 인터페이스는 선택적 기능이 유형의 기본 기능에 "혼합"되도록 허용하기 때문에 믹스인이라고 한다.

    추상 클래스는 기존 클래스에 개조할 수 없는 것과 같은 이유로 믹스인을 정의하는 데 사용할 수 없다. 클래스는 둘 이상의 상위 클래스를 가질 수 없으며 클래스 계층에 믹스인을 삽입할 합리적인 위치가 존재하지 않는다.

     

    유연성

    인터페이스를 사용하면 비계층적 타입 프레임워크를 구성할 수 있다.

    타입 계층은 구조적으로 여러 타입을 표현하는데 적합하지만, 계층을 구분하기 어려운 타입도 존재한다.

    public interface Singer {
    	AudioClip sing(Song s);
    }
    
    public interface Songwriter {
    	Song compose(int chartPosition);
    }
    
    public interface SingerSongwriter extends Singer, Songwriter {
    	AudioClip strum();
    	void actSensitive();
    }

     

    항상 이러한 수준의 유연성이 필요한 것은 아니지만 필요할 때 인터페이스는 결정적인 도움을 줄 수 있다. 위의 예를 클래스로 만든다면 부풀려진 클래스 계층 구조가 만들어질 것이다. n개의 속성이 있는 경우 지원해야 하는 조합 수는 2^n개로, 이는 조합 폭발이라 불린다. 부풀려진 클래스 계층 구조는 공통 동작을 정의하는 타입이 없기에 매개 변수 타입만 다른 메서드들이 부풀려져서 존재할 수 있다.
    인터페이스는 래퍼 클래스 관용구(Item 18)를 통해 안전하고 강력한 기능 향상을 가능하게 한다. 추상 클래스를 사용하여 타입을 정의할 때 사용할 수 있는 방법은 상속 밖에 없다.

     

    디폴트 메서드

    인터페이스 메서드 중 명백한 구현이 있는 경우 default 메서드의 형태로 프로그래머에게 제공할 수 있다. 이 방법의 예는 removeIf 메서드가 있다. Default 메서드를 제공하는 경우 @implSpec Javadoc 태그(Item 19)로 문서화하자.
    Default 메서드 사용 시 주의해야할 점은 많은 인터페이스가 equals 및 hashCode와 같은 Object 메서드를 정의하지만 이에 대고 Default 메서드를 제공하면 안된다. 또한 인터페이스는 인스턴스 필드 또는 public이 아닌 static 정적 멤버를 포함할 수 없다(private static 메서드 제외).

     

    추상 골격 구현 클래스

    인터페이스와 추상 골격 구현 클래스를 같이 제공해 인터페이스와 추상 클래스의 장점을 결합할 수 있다. 인터페이스는 몇 가지 default 메소드를 제공하는 타입을 정의하고, 골격 구현 클래스는 기본 인터페이스 메소드 위에 나머지 메소드를 구현한다. 골격 구현을 확장하면 인터페이스 구현에서 대부분의 작업이 완료되며 우리는 이를 템플릿 메서드 패턴이라 부른다.
    관례에 따라 골격 구현 클래스는 AbstractInterface라고 하며, 여기서 Interface는 구현하는 인터페이스의 이름이다. 

    Ex) Collections Framework -> AbstractCollection, AbstractSet, AbstractList 및 AbstractMap

    적절하게 설계되면 골격 구현(별도의 추상 클래스 또는 인터페이스의 기본 메서드만으로 구성됨)을 통해 프로그래머가 인터페이스의 자체 구현을 제공하기가 매우 쉬워진다.

     

    static List<Integer> intArrayAsList(int[] a) {
      Objects.requireNonNull(a);
      return new AbstractList<Integer>() {
        @Override
        public Integer get(int i) {
          return a[i]; // Autoboxing (Item 6)
        }
    
        @Override
        public Integer set(int i, Integer val) {
          int oldVal = a[i];
          a[i] = val; // Auto-unboxing
          return oldVal; // Autoboxing
        }
    
        @Override
        public int size() {
          return a.length;
        }
      };
    }

    골격 구현 클래스의 장점은 추상 클래스가 형식 정의 역할을 할 때 부과하는 심각한 제약 조건을 부과하지 않고 추상 클래스의 모든 구현 지원을 제공한다는 점이다. 골격 구현 클래스가 있는 인터페이스를 구현하는 대부분의 경우 이 클래스를 확장하는 것이 명백한 선택이지만 엄격하게는 선택 사항이다. 골격 구현을 확장하도록 클래스를 만들 수 없는 경우 클래스는 항상 인터페이스를 직접 구현할 수 있다. 이 클래스는 인터페이스 자체에 있는 default 메서드의 이점을 계속 누릴 수 있고, 추가로 골격 구현은 여전히 ​​구현자의 작업을 도울 수 있다. 

     

    정리

    인터페이스는 일반적으로 다중 구현을 허용하는 유형을 정의하는 가장 좋은 방법이다. 복잡한 인터페이스의 경우 구현하는 수고를 덜어주는 골격 구현을 제공하는 것을 꼭 고려하자.

    골격 구현은 가능한 한 인터페이스의 모든 구현자가 이를 사용할 수 있도록 인터페이스의 기본 메서드를 통해 제공되어야 한다. 즉, 인터페이스에 대한 제한은 일반적으로 골격 구현이 추상 클래스의 형태를 취하도록 요구하자.

     

    댓글

Designed by Tistory.