추상 클래스보다는 인터페이스를 우선하라
클래스, 인터페이스의 차이
클래스 : 추상 클래스를 상속받는 클래스는 반드시 추상 클래스의 하위 클래스가 된다.
인터페이스 : 어떤 클래스를 상속하든 하위 타입이 아닌 같은 타입으로 취급된다.
인터페이스는 믹스인(mixin) 정의에 안성맞춤이다
기존 클래스에 손쉽게 인터페이스를 구현할 수 있다.
반면, 기존 클래스에 추상 클래스를 끼워 넣기는 어렵다. 계층구조상 상위<->하위의 구조를 가지므로 클래스 계층구조에 혼란을 야기한다.
이러한 이유로, 인터페이스는 믹스인 정의에 안성맞춤이다.
믹스인(mixin)
대상 타입의 주된 기능에 선택적 기능을 혼합하는 것. 예를들어 Comparable은 자신을 구현한 클래스의 인스턴스끼리는 순서를 정할 수 있다
고 선택적 기능을 혼합하는 믹스인이다.
인터페이스는 계층구조가 없는 타입 프레임워크를 만들 수 있다
클래스는 구체 클래스가 추상 클래스의 하위 계층 클래스가 되지만, 인터페이스는 같은 타입으로 취급된다.
가수(Singer)와 작곡가(Songwriter) 인터페이스가 있을 때, 작곡과 노래를 모두 하는 싱어송라이터(SingerSongwriter)는
Singer와 Songwriter 인터페이스를 모두 확장하여 새로운 인터페이스로 정의할 수 있다. (+ 새로운 메서드를 추가할 수도 있다)
// 가수
public interface Singer {
void sing();
}
// 작곡가
public interface SongWriter {
void compose();
}
// 싱어송라이터 (가수 + 작곡)
public interface SingerSongwriter extends Singer, SongWriter {
// 새로운 메서드를 추가할 수도 있다
void strum();
void actSensitive();
}
디폴트 메소드에는 제약이 있다
제약 사항
- Object 메소드인 equals와 hashcode를 디폴트 메소드로 제공 안함.
- 인터페이스는 인스턴스 필드를 가질 수 없고 public이 아닌 정적 메소드를 가질 수 없음.
- 본인이 만든 인터페이스가 아니면 디폴트 메소드를 추가할 수 없음.
골격 구현(skeletal implementation) 활용
인터페이스로는 타입을 정의하고, 골격 구현 클래스는 나머지 메서드를 구현함으로써 인터페이스를 구현할 때 공통된 부분을 추상클래스로 해결할 수 있다. 이를 템플릿 메서드 패턴이라 한다.
public interface Character {
void create();
void hunt();
void levelUp();
void process();
}
Character라는 인터페이스가 존재한다.
해당 인터페이스는 생성(create), 사냥(hunt), 레벨업(levelUp)의 메서드를 가지며,
생성 - 사냥 - 레벨업을 순서대로 수행하는 process라는 메서드를 가진다.
// Warrior
public class Warrior implements Character {
@Override
public void create() {
System.out.println("create");
}
@Override
public void hunt() {
System.out.println("warrior hunt");
}
@Override
public void levelUp() {
System.out.println("levelup");
}
@Override
public void process() {
create();
hunt();
levelUp();
System.out.println("============");
}
}
// Archer
public class Archer implements Character {
@Override
public void create() {
System.out.println("create");
}
@Override
public void hunt() {
System.out.println("archer hunt");
}
@Override
public void levelUp() {
System.out.println("levelup");
}
@Override
public void process() {
create();
hunt();
levelUp();
System.out.println("============");
}
}
Warrior와 Archer 클래스를 자세히 보면, hunt 메서드를 제외하고는 모두 동일한 동작을 수행한다.
이럴 때 골격 구현 클래스를 구현하여 공통된 메서드는 골격 구현 클래스에서 구현하고, 공통되지 않은 메서드에 대해서만 상속 받은 클래스에서 구현하도록 할 수 있다.
// AbstractCharacter
public abstract class AbstractCharacter implements Character {
@Override
public void create() {
System.out.println("Character create");
}
@Override
public void levelUp() {
System.out.println("Character levelUp");
}
@Override
public void process() {
create();
hunt();
levelUp();
System.out.println("============");
}
}
해당 골격 구현 클래스를 Warrior와 Archer 클래스에 적용한다.
public class Warrior extends AbstractCharacter {
@Override
public void hunt() {
System.out.println("warrior hunt");
}
}
public class Archer extends AbstractCharacter {
@Override
public void hunt() {
System.out.println("archer hunt");
}
}
'EFFECTIVE JAVA' 카테고리의 다른 글
EFFECTIVE JAVA TIL 22 - 인터페이스는 타입을 정의하는 용도로만 사용하라 / 아이템 22 (0) | 2024.08.23 |
---|---|
EFFECTIVE JAVA TIL 19 - 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 / 아이템 19 (0) | 2024.08.23 |
EFFECTIVE JAVA TIL 18 - 상속보다는 컴포지션을 사용하라 / 아이템 18 (0) | 2024.08.23 |
EFFECTIVE JAVA TIL 17 - 변경 가능성을 최소화하라 / 아이템 17 (0) | 2024.08.23 |
EFFECTIVE JAVA TIL 16 - public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 / 아이템 16 (0) | 2024.08.22 |