자바의 다중 구현 메커니즘 : 둘다 인스턴스 메서드를 구현 형태로 제공할 수 있다 (default method)
- 인터페이스 : 다중 상속, 같은 타입 취급
- 추상클래스 : 단일 상속, 하위 클래스 (상하 관계)
1. 인터페이스의 장점
a. 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다.
- 인터페이스 : 요구하는 메서드를 추가하고, 클래스 선언에 implements 구문만 추가하면 끝이다.
- 추상클래스 : 계층 구조상 확장시킨 클래스의 공통 조상이 되어, 클래스 계층 구조를 생각해야한다.
b. 믹스인 정의에 안성맞춤이다.
믹스인 : 클래스가 구현할 수 있는 타입.
믹스인을 구현한 클래스에 원래의 '주된 타입'외에도 선택적 기능을 '혼합'하여 사용한다.
추상클래스 : 기존 클래스에 덧씌울 수 없다. 클래스가 두 부모를 섬길 수 없는 단일 상속의 특징이 있다.
Comparable, Clonable, Serializable
c. 인터페이스로는 계층 구조가 없는 타입 프레임 워크를 만들 수 있다.
public interface SingerSongWriter extends Singer, SongWriter {
void strum();
void actSensitive();
}
public abstract class Singer {
abstract void sing(String s);
}
public abstract class SongWriter {
abstract void compose(int chartPosition);
}
public abstract class SingerSongWriter {
abstract void strum();
abstract void actSensitive();
abstract void Compose(int chartPosition);
abstract void sing(String s);
}
추상 클래스로 만들면 다중상속이 불가능해, 새로운 추상클래스를 만들어서 클래스 계층을 표현할 수 밖에 없다.
따라서 이 계층구조를 만들기 위해 많은 조합이 필요하고, 결국엔 고도비만 계층 구조가 만들어진다. (조합 폭발)
d. 래퍼 클래스 관용구와 함께 사용하면 인터페이스는 기능을 향상시키는 안전하고 강력한 수단이된다.
타입을 추상클래스로 정의했을 때 : 기능 추가 방법은 상속뿐이다. -> 활용도가 떨어지고 쉽게 깨진다
래퍼클래스의 활용도가 더 높다 : 아이템 18
2. 인터페이스의 디폴트 메서드 제약
- 디폴트 메서드를 제공할 때는 @implSpec 을 붙여 문서화한다.
- equals와 hashCode는 디폴트 메서드로 정의하면 안된다.
- 인터페이스는 인스턴스 필드를 가질 수 없다.
- public이 아닌 정적 멤버도 가질 수 없다.
- 우리가 만들지 않은 인터페이스에는 디폴트 메서드를 추가할 수 없다.
3. 인터페이스와 추상골격 구현 클래스
a. 개념
인터페이스 : 타입 + 디폴트 메서드
골격구현 클래스 : 나머지 메서드들까지 구현
인터페이스 구현에 필요한 대부분의 일이 완료된다 -> 템플릿 메서드 패턴
네이밍 관례 : Abstract[Interface명] : AbstractCollection, AbstractSet, AbstractList, AbstractMap
b. 장점
추상 클래스처럼 구현을 도와주는 동시에, 추상클래스로 타입을 정의할 때 따라오는 심각한 제약에서 자유롭다.
4. 시뮬레이트한 다중 상속(simulated multiple inheritance)
골격 구현 클래스를 우회적으로 이용하는 방식
인터페이스를 구현한 클래스에서, 골격 구현을 확장한 private 내부 클래스를 정의하고, 각 메서드 호출을 내부 클래스의 인스턴스에 전달한다.
public interface Vending {
void start();
void chooseProduct();
void stop();
void process();
}
public abstract class AbstractVending implements Vending {
@Override
public void start() {
System.out.println("vending start");
}
@Override
public void stop() {
System.out.println("stop vending");
}
@Override
public void process() {
start();
chooseProduct();
stop();
}
}
public class VendingManufacturer {
public void printManufacturerName() {
System.out.println("Made By JavaBom");
}
}
public class SnackVending extends VendingManufacturer implements Vending {
InnerAbstractVending innerAbstractVending = new InnerAbstractVending();
@Override
public void start() {
innerAbstractVending.start();
}
@Override
public void chooseProduct() {
innerAbstractVending.chooseProduct();
}
@Override
public void stop() {
innerAbstractVending.stop();
}
@Override
public void process() {
printManufacturerName();
innerAbstractVending.process();
}
private class InnerAbstractVending extends AbstractVending {
@Override
public void chooseProduct() {
System.out.println("choose product");
System.out.println("chocolate");
System.out.println("cracker");
}
}
}
5. 골격 구현 작성 방법
a. 인터페이스를 잘 살펴 다른 메서드들의 구현에 사용되는 기반 메서드를 선정
b. 기반 메서드들을 사용해 직접 구현할 수 있는 메서드들을 모두 디폴트 메서드로 제공
c. 기반 메서드나 디폴트 메서드로 만들지 못한 메서드가 남아있다면, 이 인터페이스를 구현하는 골격 구현 클래스를 하나 만들어서 작성한다.
d. 골격 구현은 기본적으로 상속이므로, 설계 및 문서화 지침을 모두 따라야한다.
6. 단순 구현
- 골격 구현의 작은 변종
- 골격 구현처럼 상속을 위해 인터페이스를 구현했으나 추상클래스가 아니다.
- AbstractMap.SimpleEntry
'Dev Book Review > Effective Java' 카테고리의 다른 글
[Effective Java] item22. 인터페이스는 타입을 정의하는 용도로만 사용하라 (0) | 2020.05.05 |
---|---|
[Effective Java] item21. 인터페이스는 구현하는 쪽을 생각해 설계하라 (0) | 2020.05.05 |
[Effective Java] item 19. 상속을 고려해 설계하고 문서화하라. 그렇지 않았다면 상속을 금지하라 (0) | 2020.05.05 |
[Effective Java] Item18. 상속보다는 컴포지션을 사용하라 (0) | 2020.05.05 |
[Effective Java] item17. 변경가능성을 최소화하라 (0) | 2020.05.05 |