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

2023. 12. 2. 17:45java/이펙티브 자바

 

인터페이스의 장점

 

 1. 구현이 명백한 것은 인터페이스의 디폴트 메서드를 사용해 프로그래머의 일감을 덜어 줄 수 있다.

 

 2. 기존 클래스도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다.

자바는 단일 상속만 허용하기 때문에 추상 클래스는 제약이 매우 크지만 인터페이스는 선언한 메서드만 정의하고 그 규약을 잘 지킨다면 어떤 클래스를 상속했든 같은 타입으로 취급된다.

 

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

믹스인이란 클래스가 구현할 수 있튼 타입으로 클래스의 주된 타입 외에도 특정 선택적 생위를 제공한다고 선언하는 효과를 준다. ex) Comparable

 

 4. 계층 구조가 없는 타입 프레임워크를 만들 수 있다.

타입을 계층적으로 정의하면 수많은 개념을 구조적으로 잘 표현할 수 있지만, 구분하기 어려운 개념도 있다.

public interface Singer {
    AudioClip sing(Song s);
}

===========================================================================

public interface Songwriter {
    Song compose(int chartPosition);
}

===========================================================================

public interface SingSongwriter extends Singer, Songwriter{
    AudioClip strum();
    void actSensitive();
}

이 코드처럼 타입을 인터페이스로 정의하면 가수 클래스가 Singer와 Songwriter 모두를 구현해도 전혀 문제되지 않는다.

 

 5. 래퍼 클래스와 함께 사용하면 인터페이스는 기능을 향상 시키는 안전하고 강력한 수단이 된다.(아이템18)

 

하지만 인터페이스에도 단점이 몇가지 있다. 디폴트 메소드로 equals나 hashcode 같은 공통메소드를 제공해서는 안되고, 인스턴스 필드를 사용할 수 없다. 인스턴스 필드도 가질 수 없다.

 

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

 

인터페이스  - 타입 정의 및 필요시 디폴트 메소드 구현

public interface List<E> extends Collection<E> {
    // Query Operations

    /**
     * Returns the number of elements in this list.  If this list contains
     * more than {@code Integer.MAX_VALUE} elements, returns
     * {@code Integer.MAX_VALUE}.
     *
     * @return the number of elements in this list
     */
    int size();

    /**
     * Returns {@code true} if this list contains no elements.
     *
     * @return {@code true} if this list contains no elements
     */
    boolean isEmpty();

    .코드 생략
    .
    .
    .
}

추상 골격 구현 클래스 - 나머지 메소드 구현

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    /**
     * Sole constructor.  (For invocation by subclass constructors, typically
     * implicit.)
     */
    protected AbstractList() {
    }

    /**
     * Appends the specified element to the end of this list (optional
     * operation).
     *
     * <p>Lists that support this operation may place limitations on what
     * elements may be added to this list.  In particular, some
     * lists will refuse to add null elements, and others will impose
     * restrictions on the type of elements that may be added.  List
     * classes should clearly specify in their documentation any restrictions
     * on what elements may be added.
     *
     * @implSpec
     * This implementation calls {@code add(size(), e)}.
     *
     * <p>Note that this implementation throws an
     * {@code UnsupportedOperationException} unless
     * {@link #add(int, Object) add(int, E)} is overridden.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws UnsupportedOperationException if the {@code add} operation
     *         is not supported by this list
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this list
     * @throws NullPointerException if the specified element is null and this
     *         list does not permit null elements
     * @throws IllegalArgumentException if some property of this element
     *         prevents it from being added to this list
     */
    public boolean add(E e) {
        add(size(), e);
        return true;
    }

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public abstract E get(int index);
    .
    .
    .
    .코드 생략
}

 =>템플릿 메서드 패턴

 

// 코드 20-1 골격 구현을 사용해 완성한 구체 클래스 (133쪽)
public class IntArrays {
    static List<Integer> intArrayAsList(int[] a) {
        Objects.requireNonNull(a);

        return new AbstractList<>() {
            @Override public Integer get(int i) {
                return a[i];  // 오토박싱(아이템 6)
            }

            @Override public Integer set(int i, Integer val) {
                int oldVal = a[i];
                a[i] = val;     // 오토언박싱
                return oldVal;  // 오토박싱
            }

            @Override public int size() {
                return a.length;
            }
        };
    }
}

 

다중 상속을 시뮬레이트할 수 있다. 추상 클래스의 제약인 다중 상속을 못하는 부분을 인터페이스를 이용해 우회할 수 있다.

//추상 클래스
public abstract class AbsctractCat {
    protected abstract String sound();
    protected abstract String name();
}

=======================================================================================

//인터페이스
public interface Flyable {
    void fly();
}

=======================================================================================

//추상 골격 구현 클래스
public class AbstractFlyable implements Flyable{
    @Override
    public void fly() {
        System.out.println("날아다니기!");
    }
}

=======================================================================================

public class MyCat extends AbsctractCat implements Flyable{

    private MyFlyable myFlyable = new MyFlyable();

    @Override
    protected String sound() {
        return "냥냥!";
    }

    @Override
    protected String name() {
        return "나비!";
    }

    public static void main(String[] args) {
        MyCat myCat = new MyCat();
        System.out.println(myCat.sound());
        System.out.println(myCat.name());
        myCat.myFlyable.fly();
    }

    @Override
    public void fly() {
        this.myFlyable.fly();
    }

    private class MyFlyable extends AbstractFlyable { }
}

 

 

골격 구현은 상속용 클래스이기 때문에 아이템 19를 따라야 한다.