Study/Effective-Java

[item#20] 추상 클래스보다는 인터페이스를 우선하라

hongeeii 2023. 11. 29. 15:00
728x90
반응형

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

자바가 제공하는 다중 구현 메커니즘 : 인터페이스, 추상 클래스

자바 8부터 인터페이스도 디폴트 메서드를 제공할 수 있게되어 두 메커니즘 모두 인스턴스 메서드를 구현 형태로 제공 가능.



둘의 차이는 추상 클래스를 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 함.

=> 자바는 단일 상속만 지원하니 추상 클래스 방식으로 하면 새로운 타입을 정의하는데 제약을 안게됨.

인터페이스가 선언한 메서드를 정의하고 일반 규약을 잘 지킨 클래스라면 어떤 클래스를 상속했든 같은 타입으로 취급.

인터페이스의 장점

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

기존 클래스위에 새로운 추상 클래스를 끼워 넣기는 어럽지만 인터페이스는 implements 구문만 추가하면 끝임.

(자바 플랫폼에서도 Compareble, Iterable AutoCloseable 을 추가됐을 때도 기존 클래스가 이 인터페이스를 구현한채 릴리스 됨.)


두 클래스가 같은 추상 클래스를 확장하길 원한다면, 추상 클래스는 계층구조상 두 클래스의 공통 조상이어야함.

이 방식은 클래스 계층구조에 혼란을 일으킴.

새로 추가된 추상클래스의 모든 자손이 이를 상속하게 되는것임 => 이게 적절하지 않은 상황도 있음.

default 메서드

image


기존에 없던 메서드지만 해당 인터페이스를 지정받는 모든 클래스에 필요한 공통 메서드라면 default 메서드로 제공 가능함.

이렇게 하면 기능이 쉽게 추가될 뿐만아니라 지정받는 클래스가 깨질 일도 없음(컴파일 시 에러가 안남).

인터페이스는 믹스인(mixin) 정의에 안성맞춤

믹스인 : 클래스가 구현할 수 있느 타입, 믹스인을 구현한 클래스에 원래의 타입 외에도 특정 선택적 행위를 제공한다고 선언하는 효과를 줌.

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();
}

인터페이스로 정의하면 Singer 와 Songwirter 모두를 구현해도 문제가 되지 않고 추가로 메서드를 정의해도 문제가 되지 않음.



같은 구조를 클래스로 만들려면 가능한 조합 전부를 각각의 클래스로 정의한 고도비만 계층구조가 만들어질 수 있음

(2의n제곱(n은 속성의 개수))(조합폭발이라부름(combinatorial explosion))

기능을 향상시키는 안정하고 강력한 수단

타입을 추상 클래스로 정의해두면 그 타입에 기능을 추가하는 방법은 상속 뿐임.

상속해서 만든 클래스는 래퍼 클래스보다 활용도가 떨어지고 깨지기 쉬움.(item#18)

추상 골격 클래스

추상클래스의 장점 : 인터페이스의 default 메서드만으로 구현하지 못하는 메서드를 구현 가능.

추상골격클래스(skeletal class) : 추상클래스와 인터페이스를 같이 사용하는 방법 => 추상클래스와 인터페이스의 장점을 같이 사용할 수 있다.(잘만 사용하면)

=> 인터페이스의 구현을 도와주는 역할임.

구현이 편리함

image

추상클래스인 AbstractList 를 구현하기 위해서는 위와같이 2개의 메서드만 정의하면 되는데

인터페이스인 List 를 구현하기위에서는 아주 많은 메서드를 정의해야함.

다중 상속을 시뮬레이트

image

MyCat이 Flyable 을 지정 받고 구현하고 싶으나 이미 상속을 받는 클래스가 있다면

내부 클래스를 만들고 지정받고 싶은 interface를 지정받아 구현한 후

Flyable의 메서드를 오버라이딩 하면서 위임을 해주면 상속은 아니지만 마치 상속받은 것처럼 사용가능.

골격 구현은 상속용 클래스이기 때문에 item#19를 따라야함.

템플릿 메서드 패턴

템플릿 메서드 패턴 : 알고리즘 구조를 서브 클래스가 확장할 수 있도록 템플릿으로 제공하는 방법

=> 추상클래스는 템플릿을 제공하고 하위 클래스는 구체적인 알고리즘을 제공

image

image

FileProcessor 의 process 메서드가 양식을 정해주면

구체적인 구현은 하위 클래스인 Plus 가 getResult 메서드를 재정의해서 구현하고 있음.

템플릿 콜백 패턴

굳이 상속을 해야하나? 싶을때는

위의 코드를 아래와 같이 수정 가능함.

image

image


디폴트 메서드와 Object메서드

https://mail.openjdk.org/pipermail/lambda-dev/2013-March/008435.html

Object_method

Object 메서드는 아예 못 만들게 컴파일 에러가 남.

왜막았을까?

  1. 디폴트 메서드는 새로운 기능을 추가할 때 기존 클래스들은 그대로 유지하면서도 간단하게 추가 기능을 만들 수 있도록 만들었는데

    Object 메서드는 설계의 변화나 위험을 가져다 줄 수 있기때문에 막았다고 함.
  2. 복잡도를 증가시킬 수 있음.

    상속한 메서드 선택의 규칙

    하나. 클래스가 이김.

    둘. 더 자세한 인터페이스가 이김.

  3. 사실상 토이프로젝트에나 필요한 기능임.

    toString 이나 equals 등 Object 메서드는 데이터가 있을 때나 의미가 있는데 인터페이스 자체만으로는 의미를 찾기 어려움.
  4. 깨지기 쉬움

    인터페이스의 변경으로 지정받던 클래스의 동작이 깨질 수 있는 위험이 있음.
728x90
반응형