다중 구현 메커니즘
자바가 제공하는 다중 구현 메커니즘은 인터페이스, 추상클래스 2가지다. 자바 8부터 인터페이스도 디폴트 메서드를 제공할 수 있게되었다.
디폴트 메서드란?
https://devfunny.tistory.com/350
추상클래스
추상 클래스와 인터페이스의 가장 큰 차이는 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야한다는 점이다. 자바는 단일 상속만 가능하므로, 추상 클래스 방식은 새로운 타입을 정의하는데 큰 제약을 갖게된다. 두 클래스가 같은 추상 클래스를 상속한다면 그 추상 클래스는 계층 구조 상 두 클래스의 공통 조상이어야 한다. 이 방식은 클래스 계층 구조에 커다란 혼란을 일으킨다. 새로 추가된 추상 클래스의 모든 자손이 이를 상속하게 되어 강제성이 생겨버린다.
인터페이스
인터페이스는 선언한 메서드를 모두 정의하고 그 일반 규약을 잘 지킨 클래스라면 다른 어떤 클래스를 상속했든 같은 타입으로 취급한다. 기존 클래스에서 손쉽게 새로운 인터페이스를 구현해넣을 수 있다. (public class implents interface) 인터페이스로는 계층 구조가 없는 타입 프레임워크를 만들 수 있다. 하나의 클래스에서 2개 이상의 인터페이스 모두를 구현해도 전혀 문제가 없다.
인터페이스의 메서드 중 구현 방법이 명백한 것이 있다면, 그 구현을 디폴트 메서드로 제공하여 프로그래머들의 일감을 덜어줄 수 있다.
디폴트 메서드의 사용 예제
https://devfunny.tistory.com/413
디폴트 메서드를 제공할때는 상속하려는 사람을 위한 설명을 @implSpec 자바독 태그를 붙여 문서화 해야 한다. 디폴트 메서드에도 제약은 있다. 인스턴스 필드를 가질 수 없고, public 이 아닌 정적 멤버도 가질 수 없다. (private 정적 메서드는 예외)
템플릿 메서드 패턴
템플릿 메서드 패턴의 자세한 내용은 아래 포스팅을 참고하자.
https://devfunny.tistory.com/899
인터페이스와 추상 골격 구현 클래스를 함께 제공하는 방식으로 인터페이스와 추상 클래스의 장점을 모두 취할수도 있다. 인터페이스로는 타입을 정의하고, 필요하면 디폴트 메서드 몇개도 함께 제공한다. 그리고 골격 구현 클래스는 나머지 메서드들까지 구현한다. 이렇게 단순히 골격 구현만 확장하는 것만으로 이 인터페이스를 구현하는데 필요한 일이 대부분 완료된다.
- 인터페이스 이름 : Interface
- 골격 구현 클래스 이름 : AbstractInterface (예시: 컬렉션 프레임워크의 AbstractCollection, AbstractSet 등)
예시. AbstractList.java
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
...
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) 기반 메서드들을 사용해 직접 구현할 수 있는 메서드를 모두 디폴트 메서드로 제공한다.
interfaceTest.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 메서드를 각 클래스 별로 재정의했다.
'Book > Effective Java' 카테고리의 다른 글
[교재 EffectiveJava] 아이템 22. 인터페이스는 타입을 정의하는 용도로만 사용하라 (0) | 2021.10.23 |
---|---|
[교재 EffectiveJava] 아이템 21. 인터페이스는 구현하는 쪽을 생각해 설계하라 (0) | 2021.10.22 |
[교재 EffectiveJava] 아이템 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (0) | 2021.10.19 |
[교재 EffectiveJava] 아이템 18. 상속보다는 컴포지션을 사용하라 (0) | 2021.10.18 |
[교재 EffectiveJava] 아이템 17. 변경 가능성을 최소화하라 (0) | 2021.10.17 |