본문 바로가기
Language/Effective java

클래스와 인터페이스

by y.j 2022. 6. 14.
728x90

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

추상클래스는 정의 타입을 항상 하위 클래스로 하여야 한다. 하지만, 인터페이스같은 경우에는 어떤 클래스를 상속해도 같은 타입으로 취급한다.

 

 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다. 인터페이스가 요구하는 메서드를 추가하고, 클래스 선언에 implements 구문만 추가하면 끝이다. 반면 추상클래스는 확장하기 어렵다. 추상 클래스를 확장하기 원한다면 상속 받는 모든 클래스의 공통조사이여야 한다. 적절하지 않은 상황에도 강제로 해야만 하는 상황이 생긴다.

 

 인터페이스는 믹스인(mixin)정의에 안성맞춤이다. 믹스인 정의는 클래스가 구현 할 수 있는 타입으로 부모클래스가 되지 않으면서 선택적 행위를 제공해준다. Comparable은 자신을 구현한 클래스끼리 순서를 정의할 수 있다고 선언할 수 있다. 이처럼 대상 타입의 주된 기능에 선택적 기능을 혼합한다.

 

인터페이스로는 계층구조가 없는 타입 프레임워크를 만들 수 있다. 타입을 계층적으로 정의하면 수 많은 개념을 구조적으로 잘 표현 할 수 있지만, 현실에는 계층을 엄격히 구분하기 어려운 개념도 있다. 

public interface SInger {
    AudioClip sing(Song s);
}
public interface SongWriter {
    Song compose(int charPosition);
}

Sing과 SongWriter클래스가 따로 있지만 실제로는 노래와 작곡을 둘 다 하는 객체를 표현하기 위해서 다중 상속을 받을 수 있다.

 

인터페이스는 기능을 향상 시키는 안전하고 강력한 수단이 된다. 타입을 추상클래스로 정의해두면 그 타입에 기능을 추가하는 방법은 상속뿐이다. 상속해서 만든 클래스는 래퍼클래스보다 활용도가 떨어지고 깨지기 쉽다. 한편 인터페이스와 추상클래스를 함께 이용하고 인터페이스와 추상 클래스의 모든 장점을 취하는 방법도 있다. 인터페이스로는 타입을 정의하고 필요하면 디폴트 메서드도 제공하며 나머지는 골격 구현 클래스에서 제공한다. [ 템플릿메서드 패턴 ]

 

완벽히 동작하는 List구현체를 반환하는 정적 팩터리 메서드로 AbstractionList골격구조를 활용하는 방법도 있다. 

* Interface의 골격 구현 클래스의 이름은 AbstractInteface라고 짖는다.

public class IntArrayList {
    static List<Integer> intArrayAsList(int[] a) {
        Objects.requireNonNull(a);

        return new AbstractList<Integer>() {
            @Override
            public Integer get(int index) {
                return a[index];
            }

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

 

골격 구현 클래스는 구현을 도와주는 동시에 추상클래스로 타입을 정의할 때 따라오는 심각한 제약에서는 자유롭다. 구조상 골격 구현 클래스를 사용하지 못하더라도 인터페이스를 직접 제공하는 디폴트 메서드의 장점을 여전히 누릴 수 있다. 골격 구조를 우회적으로도 이용 할 수 있다. 골격 구현 클래스를 상속받은 private 내부 클래스는 정의하고 인스턴스화 하는 것이다( 시뮬레이트한 다중 상속 ).

 


골격구현 작성법

1. 인터페이스를 잘 살펴 메서드들의 구현에 사용되는 기반 메서드를 정한다.

2. equals, hashCode, toString은 재정의한다.

3. 필요하면 private 필드와 메서드를 추가해도 된다.

public abstract class AbstractMapEntry implements Map.Entry {

    @Override
    public Object getKey() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Object getValue() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Object setValue(Object value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == this)
            return true;
        if(!(obj instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry) obj;
        return Objects.equals(e.getKey(), getKey())
                && Objects.equals(e.getValue(), getValue());
    }

    @Override
    public String toString() {
        return getKey() + "=" + getValue();
    }
}

 

골격 구현은 기본적으로 상속해서 사용하는 것을 가정하므로 문서화 지침을 모두 따라야 한다.

 

단순구현은 골격 구현의 작은 변종으로 추상클래스가 아닌 인터페이스를 구현한 가장 단순한 구현이다. 필요시에 확장해서 사용할 수 있다.

728x90

'Language > Effective java' 카테고리의 다른 글

클래스와 인터페이스  (0) 2022.06.17
클래스와 인터페이스  (0) 2022.06.17
클래스와 인터페이스  (0) 2022.06.14
클래스와 인터페이스  (0) 2022.06.12
클래스와 인터페이스  (0) 2022.06.12

댓글