티스토리 뷰
728x90
반응형
추상 클래스보다는 인터페이스를 우선하라
- Java 8버전부터는 인터페이스에도 default 메서드 및 static 메서드를 제공하고 있습니다.
- default 메서드와 static 메서드는 body를 가질 수 있으며 static 메서드의 경우 인터페이스명.메서드명으로 호출할 수 있습니다.
- 인터페이스에 정의되는 메서드 중 구현 방법이 명확하고 공통적인 경우라면 default 메서드로 만들게되면 하위 클래스에서 각각 재정의할 필요가 없어집니다.
💡예제 코드
- 아래 예제 코드에서 static 메서드는 구현 클래스에서 재정의할 수 없으며 호출시 인터페이스명.메서드명으로 호출할 수 있습니다.
public interface Calculator {
default void add(int x, int y) {
System.out.println("Calculator Call default Method : " + (x + y));
}
static void minus(int x, int y) {
System.out.println("Calculator Call static Method : " + (x - y));
}
}
public class CalculatorImpl implements Calculator {
@Override
public void add(int x, int y) {
System.out.println("CalculatorImpl Call Override default Method : " + (x + y));
}
}
public class EffectiveJavaApplication {
public static void main(String[] args) throws Exception {
Calculator calculator = new CalculatorImpl();
calculator.add(10, 20); // CalculatorImpl Call Override default Method : 30
Calculator.minus(10, 6); // Calculator Call static Method : 4
}
}
인터페이스의 장점
💡기존 클래스에도 쉽게 새로운 인터페이스를 구현해 넣을 수 있습니다.
- Food 인터페이스를 구현하는 Apple 클래스가 있습니다. Apple 클래스는 객체간 비교를 통해 정렬할 수 있도록 Comparable을 구현하고자 한다면 implements 위에 인터페이스명만 추가를 하면 쉽게 재정의할 수 있습니다.
- 하자민 만약 Food가 추상 클래스였다면 새로운 추상 클래스를 끼워넣어기는 어렵습니다. 자바는 다중 상속을 지원하지 않습니다.
public interface Food {
String getName();
}
public class Apple implements Food, Comparable<Apple> {
private final String name;
private final int price;
public Apple(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String getName() {
return this.name;
}
@Override
public int compareTo(Apple o) {
return Integer.compare(this.price, o.price);
}
}
💡인터페이스는 믹스인(mixin) 정의에 안성맞춤입니다.
- 믹스인이란 대상 타입의 주된 기능에 선택적 기능을 혼합한다고해서 믹스인이라고 부릅니다.
- 믹스인 타입정의는 믹스인을 구현한 클래스에 주된 타입외에 특정 행위를 제공한다고 선언하는 효과를 주는데, 위에서 작성한 Apple 하위 클래스의 Comparable이 믹스 인터페이스입니다.
- 그래서 Apple 클래스는 Food 타입이라는 주된 타입외에 비교하여 정렬할 수 있다고 선언할 수 있습니다. 하지만 추상 클래스로는 믹스인을 정의할 수 없습니다.
💡인터페이스로는 계층 구조가 없는 타입 프레임워크를 만들 수 있습니다.
- 현실에서 계층으로 구분하기 힘든 개념들을 묶어 제 3의 인터페이스로 정의할 수 있습니다.
- 가수 인터페이스와 작곡가 인터페이스가 있습니다. 하지만 가수겸 작곡가를 할 수도 있습니다. 그런 경우 SingSongWriter라는 제 3의 인터페이스를 정의하고 Singer, SongWriter를 상속받으면 높은 유연성을 재공할 수 있습니다.
- 이러한 경우를 인터페이스를 사용하지 않고 클래스를 사용하면 경우의 수가 너무 많이 나올 수 있습니다. 이러한 경우를 조합 폭발(combinatorial exposion)이라는 현상입니다.
public interface Singer {
default void sing(String name) {
System.out.println(name + "인 노래를 부릅니다.");
}
}
public interface SongWriter {
default void write() {
System.out.println("작곡을 합니다.");
}
}
public interface SingSongWriter extends Singer, SongWriter {
@Override
default void sing(String name) {
// 새로운 행위
}
@Override
default void write() {
// 새로운 행위
}
}
💡래퍼 클래스와 함께 쓰면 기능 향상이 안전하고 강력합니다.
- 래퍼 클래스를 이용해 Composition으로 사용을 하면 기능 보완 및 확장이 편리하면서 위험성도 없습니다. 하지만, 추상 클래스로 정의할 경우 기능 추가를 할려면 상속밖에 답이 없습니다.
💡제약 사항
- Object 클래스의 equals, hashCode와 같은 메서드들은 default 메서드로 제공해서는 안됩니다.
- 인터페이스는 인스턴스 필드를 가질 수 없습니다. 단 public static final 변수를 사용할 수 있으며 해당 키워드는 생략할 수 있습니다.
- 위에서 말한 정적 멤버는 가질 수 있으나 private 정적은 가질 수 없습니다.
- 우리가 구현하지 않은 인터페이스에는 디폴트 메서드를 추가할 수 없습니다.
추상 골격 구현
- 인터페이스와 추상 골격 구현을 동시에 작성해서 인터페이스와 추상 클래스의 장점을 모두 가질 수 있습니다.
- 인터페이스로는 타입을 정의하고, 필요에 따라 default 메서드를 정의합니다.
- 골격 구현 클래스는 나머지 메서드들까지 구현합니다.
- 골격 구현 클래스를 확장하는 것만으로 인터페이스 구현의 대부분이 완료되며 이를 템플릿 메서드 패턴이라고 합니다.
- 자바 라이브러리에선 콜렉션 프레임워크인 AbstractCollection, AbstractSet 등이 골격 구현 클래스입니다.
💡예제 코드
- Warrior 인터페이스로 타입이 정의되어 있고, AbstractWarrior 추상 클래스를 만들어 equals, hashCode getter, setter들을 만들어 골격을 구현했습니다.
- attack, guard, applyItem등의 메서드는 사용자가 직접 하위 클래스를 구현해야 합니다.
- 추상 골격 클래스는 인터페이스에서 구현에 사용될 기반 메서드를 선정해 골격 구현 클래스 내부의 추상 메서드가 됩니다. 그리고 기반 메서드를 통해 직접 구현이 가능한 메서드는 모두 인터페이스의 default 메서드가 되고, 기반 메서드/디폴트 메서드가 되지 못한 equals,hashCode, setter, getter등을 골격 구현 클래스에서 구현하면 됩니다.
public interface Warrior {
void attack();
void guard();
void applyItem(Item item);
default void taunt() {
System.out.println("약올리기~");
}
}
@Setter
@Getter
@ToString
public abstract class AbstractWarrior implements Warrior {
private String name;
private int hp;
private int mp;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Warrior)) return false;
Warrior that = (Warrior) o;
return Objects.equals(getHp(), that.getHp())
&& Objects.equals(getMp(), that.getMp())
&& Objects.equals(getName(), that.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getHp(), getMp());
}
}
💡정리
- 다중 상속이 필요한 경우 인터페이스를 사용하면 됩니다.
- 안터페이스가 복잡할 경우 골격 구현을 가이 제공하는 방법을 고려해봐야 합니다.
- 골격 구현은 가능한 인터페이스의 default 메서드로 제공해서 인터페이스 구현체가 활용하는게 좋습니다.
참고 자료)
https://catsbi.oopy.io/1a9e0c15-464d-436d-806c-3713e6c4e224
728x90
반응형
'스터디 > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바 - Item22. 인터페이스는 타입을 정의하는 용도로만 사용하라. (0) | 2022.07.16 |
---|---|
이펙티브 자바 - Item21. 인터페이스는 구현하는 쪽을 생각해 설계하라. (0) | 2022.07.16 |
이펙티브 자바 - Item19. 상속을 고려해 설계하고 문서화하라. 그렇지 않다면 상속을 금지하라. (0) | 2022.07.13 |
이펙티브 자바 - Item18. 상속보다는 컴포지션을 사용하라. (0) | 2022.07.13 |
이펙티브 자바 - Item17. 변경 가능성을 최소화하라. (0) | 2022.07.12 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- redis sorted set으로 대기열 구현
- @ControllerAdvice
- spring boot redis 대기열 구현
- 서비스 기반 아키텍처
- pipe and filter architecture
- transactional outbox pattern
- spring boot excel download paging
- 레이어드 아키텍처란
- 트랜잭셔널 아웃박스 패턴 스프링부트
- polling publisher spring boot
- java ThreadLocal
- redis sorted set
- spring boot excel download oom
- spring boot poi excel download
- spring boot redisson sorted set
- space based architecture
- redis 대기열 구현
- microkernel architecture
- java userThread와 DaemonThread
- spring boot 엑셀 다운로드
- transactional outbox pattern spring boot
- 자바 백엔드 개발자 추천 도서
- spring boot redisson destributed lock
- pipeline architecture
- 공간 기반 아키텍처
- 람다 표현식
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- spring boot redisson 분산락 구현
- JDK Dynamic Proxy와 CGLIB의 차이
- service based architecture
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
글 보관함