일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- IT
- spring
- 이펙티브 자바
- 깃
- github
- 프로그래밍
- 세마포어
- OS
- 깃허브
- 신입
- package-private
- 신입사원
- 공채
- 뮤텍스
- java
- 자바
- CS
- Effective Java
- 컴퓨터공학
- 메모리
- 개발
- 운영체제
- 우리카드
- Public
- 알고리즘
- 스터디
- 컴퓨터과학
- 정보처리기사
- 디지털
- 스프링
- Today
- Total
주니어 개발자 성장기
아이템 20. 추상클래스보다 인터페이스를 우선하라. 본문
개요
추상클래스 대신 인터페이스를 우선적으로 활용하자. 인터페이스의 장점이 더 많다.
기존 클래스에도 손쉽게 새로운 인터페이스를 구현해넣을 수 있다.
2개 이상의 클래스를 동시에 상속받는 것은 불가능하다. 여러 클래스를 상속 받아야 하는 클래스가 이미 추상 클래스를 상속받았다면 이미 둘 간의 계층구조가 생겨 상속에 어려움이 생긴다. 반면 인터페이스는 한 클래스에서 여러번 구현하게 할 수 있고 기존에 어떤 클래스를 상속받고 있는 지에 관계 없이 기능을 추가할 수 있다.
인터페이스는 믹스인이 가능하다.
믹스인이란 클래스가 구현할 수 있는 타입으로, 믹스인을 구현한 클래스에 원래의 ‘주된 타입’ 외에도 특정 선택적 행위를 제공한다고 선언하는 효과를 준다.
인터페이스를 통해 어떤 클래스의 갖고 있는 주된 역할외에 제공하고 싶은 부가 기능을 선택 제공할 수 있다.
예를 들어서 Autocloseable
, Comparable
, Serializable
등이 있다. 추상 클래스는 2개 이상 상속이 불가능하기 때문에 믹스인으로 쓸 수 없다.
계층구조가 없는 타입 프레임워크를 만들 수 있다.
관계(계층구조가)가 명확하지 않은 경우가 있을 수도 있다. 책에서는 Singer
와 Songwriter
를 예시로 들었다.
인터페이스도 디폴트 메서드를 제공할 수 있다.
자바 8부터 인터페이스도 디폴트 메서드를 제공할 수 있다. 해당 인터페이스를 구현하는 클래스에 메서드를 추가하고 싶은 경우, 보통 public하게 다른 곳에서 참조해서 모두 변경해야 하는 경우가 많기 때문에 추가하게 된다면 해당 인터페이스를 구현하는 클래스들이 모두 깨지게 된다. 이런 경우, 공통적으로 제공하고 싶은 메서드를 디폴트 메서드로 제공하면 된다. 해당 디폴트 메서드를 깨진 클래스들에서 구현하지 않더라도 인터페이스에서 디폴트로 구현체를 넣어주기 때문이다. 또한 stactic
메서드도 제공할 수 있다.
디폴트 메서드도 만능이 아니다.
디폴트 메서드는 필드를 사용할 수 없기 때문에 필드를 사용해야 하는 경우는 디폴트 메서드로 구현이 불가능하다. 골격 클래스와 같이 사용하면 이런 단점을 보완할 수 있다.
래퍼 클래스 관용구(아이템 18)와 함께 사용하면 인터페이스는 기능을 향상시키는 안전하고 강력한 수단이 된다.
아이템 18 참조
인터페이스와 추상 골격 구현(skeletal implementation) 클래스를 함께 제공하는 식으로 인터페이스와 추상 클래스의 장점을 모두 취하는 방법도 제공한다.
추상 골격 구현 클래스란 추상 클래스로서 인터페이스를 구현하지만 일부 로직은 완전한 구현체를 제공하고 일부 로직은 사이 사이에 구현해야할 틈이 있는 템플릿 역할을 하는 메서드는 구현이 되어있다. 템플릿 중간에는 하위클래스에서 오버라이딩 할 수 있는 부분들을 남겨 놓은 클래스다. 즉, 추상 클래스가 제공하는 기능들을 활용하면서 쉽게 인터페이스를 구현할 수 있게 도와주는 역할을 한다.
대표적인 예시로 AbstractCollection
, AbstractSet
, AbstractMap
, AbstractList
등이 있다.
인터페이스인 List
를 구현하기 위해서는 20개가 넘는 메서드를 구현해야 한다. 하지만 AbstractList
를 상속한다면 단 2개의 메서드만 구현해주면 된다. 즉, 상속해서 사용하는 프로그래머에게 미리 쓸만한 부분들을 미리 구현해두어 수고를 덜 수 있게 하는 것이다.
하지만, 추상 골격 구현 클래스를 사용할 때는 상속을 이용할 때의 주의할 점들을 반드시 기억하고 적용시켜줘야 한다.(아이템 18, 아이템 19)
시뮬레이트한 다중 상속
위에서 언급한 추상 골격 구현 클래스를 이용해 이미 다른 클래스를 상속한 클래스에서 마치 여러개를 상속한 것 처럼 쓸 수도 있다. 다음 예시를 보며 이해해 보자.
//AbstractDog 추상 클래스
public abstract class AbstractDog {
public abstract String bark();
public abstract String move();
}
// AbstractDog을 상속한 Dog 클래스
public class Dog extends AbstractDog {
@Override
public String bark() {
return "멍멍";
}
@Override
public String move() {
return "강아지가 움직인다.";
}
}
다음 코드에서 Dog에서 다른 클래스를 확장(상속)하는건 불가능하다. 하지만, 추상 골격 구현 클래스를 이용해 마치 상속한 것 처럼 사용할 수 있다. 아래 AbstractFlyable
클래스의 기능을 추가하기 원한다고 가정하자.
// Flyable 인터페이스
public interface Flyable {
String fly();
}
// AbstractFlyable 클래스
public class AbstractFlyable implements Flyable {
@Override
public String fly() {
return "날아라.";
}
}
//
절차는 다음과 같다.
Flyable
인터페이스를 구현한다.AbstractFlyable
클래스를 상속받는private
이너 클래스를 만든다.- 해당 이너 클래스를
private
멤버 필드로 선언한다. - 구현이 필요한 메서드에서 방금 선언한 멤버 필드로 해당 메서드를 호출한다.
완성된 코드를 확인해보자.
public class Dog extends AbstractDog implements Flyable {
private MyFlyable myFlyable = new MyFlyable();
@Override
public String bark() {
return "멍멍";
}
@Override
public String move() {
return "강아지가 움직인다.";
}
@Override
public String fly() {
return myFlyable.fly();
}
private class MyFlyable extends AbstractFlyable {
// 해당 메서드는 반드시 오버라이딩할 필요는 없다.
@Override
public String fly() {
return "내가 날아요.";
}
}
}
결과적으로 Flyable
의 기능을 확장할 수 있게 되었고 마치 2개의 클래스를 상속한 것과 같은 효과를 낼 수 있다.
'Java > 이펙티브 자바' 카테고리의 다른 글
아이템 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2023.11.19 |
---|---|
아이템 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라. (0) | 2023.09.17 |
아이템 18. 상속보다는 컴포지션을 사용하라. (0) | 2023.09.17 |
아이템 17. 변경 가능성을 최소화하라. (0) | 2023.09.17 |
아이템 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라. (0) | 2023.09.10 |