[교재 EffectiveJava] 아이템 20. 추상 클래스보다는 인터페이스를 우선...

[교재 EffectiveJava] 아이템 20. 추상 클래스보다는 인터페이스를 우선...

728x90

다중 구현 메커니즘

자바가 제공하는 다중 구현 메커니즘은 인터페이스, 추상클래스 2가지다. 자바 8부터 인터페이스도 디폴트 메서드를 제공할 수 있게되었다.

디폴트 메서드란?

https://devfunny.tistory.com/350

추상클래스

추상 클래스와 인터페이스의 가장 큰 차이는 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야한다는 점이다. 자바는 단일 상속만 가능하므로, 추상 클래스 방식은 새로운 타입을 정의하는데 큰 제약을 갖게된다. 두 클래스가 같은 추상 클래스를 상속한다면 그 추상 클래스는 계층 구조 상 두 클래스의 공통 조상이어야 한다. 이 방식은 클래스 계층 구조에 커다란 혼란을 일으킨다. 새로 추가된 추상 클래스의 모든 자손이 이를 상속하게 되어 강제성이 생겨버린다.

인터페이스

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

기존 클래스에서 손쉽게 새로운 인터페이스를 구현해넣을 수 있다. (public class implents interface) 인터페이스로는 계층 구조가 없는 타입 프레임워크를 만들 수 있다. 하나의 클래스에서 2개 이상의 인터페이스 모두를 구현해도 전혀 문제가 없다.

인터페이스의 메서드 중 구현 방법이 명백한 것이 있다면, 그 구현을 디폴트 메서드로 제공하여 프로그래머들의 일감을 덜어줄 수 있다.

디폴트 메서드의 사용 예제

https://devfunny.tistory.com/413

디폴트 메서드를 제공할때는 상속하려는 사람을 위한 설명을 @implSpec 자바독 태그를 붙여 문서화 해야 한다. 디폴트 메서드에도 제약은 있다. 인스턴스 필드를 가질 수 없고, public 이 아닌 정적 멤버도 가질 수 없다. (private 정적 메서드는 예외)

템플릿 메서드 패턴

인터페이스와 추상 골격 구현 클래스를 함께 제공하는 방식으로 인터페이스와 추상 클래스의 장점을 모두 취할수도 있다. 인터페이스로는 타입을 정의하고, 필요하면 디폴트 메서드 몇개도 함께 제공한다. 그리고 골격 구현 클래스는 나머지 메서드들까지 구현한다. 이렇게 단순히 골격 구현만 확장하는 것만으로 이 인터페이스를 구현하는데 필요한 일이 대부분 완료된다.

인터페이스 이름 : Interface

골격 구현 클래스 이름 : AbstractInterface (예시: 컬렉션 프레임워크의 AbstractCollection, AbstractSet 등)

예시. AbstractList.java

public abstract class AbstractList extends AbstractCollection implements List { ... public abstract E get(int index); public E set(int index, E element) { throw new UnsupportedOperationException(); } public abstract int size(); }

골격 구현 작성 방법

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

public interface Test { public void get(int value); public void set(int value); }

2) 기반 메서드들은 골격 구현에서는 추상 메서드가 된다.

3) 기반 메서드들을 사용해 직접 구현할 수 있는 메서드를 모두 디폴트 메서드로 제공한다.

interface Test.java

public interface Test { public void get(int value); public void set(int value); default void remove(int value) { System.out.println("remove : " + value); } }

4) equals, hashCode와 같은 Object 의 메서드는 디폴트 메서드로 제공하면 안된다.

5) 인터페이스의 메서드가 모두가 기반 메서드와 디폴트 메서드가 된다면 골격 구현 클래스를 별도로 만들 이유는 없다.

6) 기반 메서드나 디폴트 메서드로 만들지 못한 메서드가 남아있다면, 이 인터페이스를 구현하는 골격 구현 클래스를 하나 만들어 남은 메서드들을 작성해 넣는다.

abstract class AbstractTest.java

/** * 추상 골격 구현 클래스 */ public abstract class AbstractTest implements Test { @Override public void get(int value) { System.out.println("get : " + value); } }

class TestSub.java

public class TestSub extends AbstractTest implements Test { @Override public void set(int value) { System.out.println("set : " + value); } }

class TestSub2.java

public class TestSub2 extends AbstractTest implements Test { @Override public void set(int value) { System.out.println("set2 : " + value); } }

6) 골격 구현 클래스에는 필요하면 public 이 아닌 필드와 메서드를 추가해도 된다.

7) 결과

위 메서드를 실행하는 Main Class

public class Main { public static void main(String[] args) { TestSub testSub = new TestSub(); testSub.get(1); // get : 1 testSub.set(2); // set : 2 testSub.remove(2); // remove : 2 TestSub2 testSub2 = new TestSub2(); testSub2.get(1); // get : 1 testSub2.set(2); // set2 : 2 testSub2.remove(2); // remove : 2 } }

결국 TestSub.java 와 TestSub2.java 의 get 메서드는 공통 로직이기 때문에 AbstractTest 에서 구현되었고, 이를 상속한 각 TetsSub.java, TestSub2.java 는 아직 구현하지 않은 set 메서드를 각 클래스 별로 재정의했다.

from http://devfunny.tistory.com/551 by ccl(A) rewrite - 2021-10-20 20:01:11