20. 추상 클래스보다는 인터페이스를 우선하라
다중 구현 시 인터페이스를 우선 사용하자 Skeletal Implementation(골격구현 = 인터페이스 + 추상 클래스)를 활용하여 공통 부분을 만들자
📌 1. 발표 전 알아야 할 개념
✅ 인터페이스
클래스들이 구현해야 하는 동작을 정의하는 추상 자료형
implements
를 사용하여 구현필드는
public static final
Java 8 : default, static 메서드 포함 가능
Java 9 : private 메서드도 포함 가능
✅ 추상 클래스
하나 이상의 추상 메서드를 포함하는 클래스
추상 메서드가 없어도
abstract
로 선언 가능extends
를 사용하여 상속하며, 일반적으로 상속 구조에서 공통 기능을 구현하는 기본 클래스로 사용함한 클래스는 하나의 추상 클래스만 상속 가능! (단일 상속)
🔗 전체 예제 코드 : [Item19] 테스트 코드
📕 2. 왜 인터페이스를 우선해야 할까?
2-1. 기존 클래스에서도 쉽게 인터페이스를 구현할 수 있다
(인터페이스) 기존 클래스를 변경하지 않고도 새로운 기능을 추가할 수 있다
(추상클래스) 다중 상속이 불가하고, 재사용이 어렵다
// 기존의 Car 클래스
public class Car {
private int speed=0;
public final void drive() {
System.out.println("driving");
accelrate();
System.out.println("speed is " + speed);
}
public int getSpeed() {
return speed;
}
}
만약 내가 Car를 상속 받는 ElectricCar를 만들고 Charge 기능을 추가하고 싶다면?
// Electric interface
// 전기차 충전 기능을 추가하기 위한 인터페이스
public interface Electric {
void charge();
}
// Car 클래스는 수정하지 않고, Electric 기능을 추가한 ElectricCar 생성
public class ElectricCar extends Car implements Electric {
@Override
public void charge() {
System.out.println("전기차가 충전 중입니다.");
}
}
인터페이스를 사용하여 기존 클래스를 수정하지 않고 새로운 클래스를 만들 수 있다. 또한 이렇게 만든 인터페이스는 다른 클래스에서도 적용 가능하다.
2-2. Mixin(믹스인)에 좋다
Mixin
: 주 기능 외에 선택적 기능을 'mixin(혼합)'할 수 있게 해주는 타입(인터페이스) 다중 구현이 가능하기 때문에 여러 기능 조합 가능
(추상클래스) 다중 상속 불가능, 믹스인 패턴 사용 불가
public interface Electric {
void charge();
}
public interface SelfDrive {
void autoPilot();
}
인터페이스로 기능을 구현할 경우, 다중 구현이 가능하다.
// Electric과 SelfDrive 모두 기능 포함 가능
public class TeslaCar extends Car implements Electric, SelfDrive{
@Override
public void charge() {
System.out.println("Tesla car charge");
}
@Override
public void autoPilot() {
System.out.println("Tesla car autoPilot");
}
}
mixin을 구현하는데 가장 이상적이다!
2-3. 계층 구조가 없는 타입 프레임워크 생성 가능
(인터페이스)같은 계층(레벨)의 다양한 타입을 자유롭게 만들 수 있다
(추상클래스) 강제적인 상속 구조가 생겨 계층 구조가 고정된다
// Car 인터페이스
public interface Car {
void move();
}
public class TeslaCar implements Car {
@Override
public void move() {
System.out.println("Tesla Car");
}
}
public class PorscheCar implements Car {
@Override
public void move() {
System.out.println("Prosche Car");
}
}
🦴 3. Skeletal Implementation = 인터페이스 + 추상 클래스
3-1. Skeletal Implementation (골격 구현) 💥
인터페이스를 구현하고, 인터페이스의 대부분 메서드를 구현한 추상클래스를 제공하는 방식
인터페이스를 추상 클래스의 장점으로 보완
AbstractInterface
로 이름을 구현하는 것이 일반적이다인터페이스의 유연성 + 추상 클래스의 코드 재사용성
3-2. Skeletal Implementation을 왜 사용할까?
Java8 이전, 인터페이스는 다중 구현은 가능하지만 메서드 구현은 불가했다
모든 구현을 하위 클래스에서 직접 해야 하기 때문에 코드의 중복 발생
Skeletal Implementation 클래스를 제공하면서 중복 코드를 줄일 수 있음
**[확장] ** 자바 9부터는 인터페이스 내에 private
메서드 정의, default
메서드에서 활용이 가능해졌는데..?
인터페이스는 필드, 상태를 가질 수 없다
static final
상수만 선언 가능하고, 인스턴스 필드를 가질 수 없다. 변수가 필요하다면 추상 클래스가 필요!인터페이스는 여전히 생성자를 가질 수 없다
skeletal Implementation을 통해 복잡한 구조에서 코드 재사용을 더 효과적으로 관리할 수 있다.
3-3. Skeletol Implementation의 예시
// Car interface
public interface Car {
void move();
void alarm();
}
// Skeletal Implementation
// Skeletal Implementation
// Car 인터페이스를 보완하는 추상 클래스
public class AbstractCar implements Car {
// 공통 기능에 대한 기본 구현 제공
@Override
public void move() {
System.out.println("AbstractCar move");
}
@Override
public void alarm() {
System.out.println("AbstractCar alarm");
}
}
// HyundaiCar Class
public class HyundaiCar extends AbstractCar {
// 필요한 기능만 오버라이딩 가능
@Override
public void alarm() {
System.out.println("Hyundai alarm");
}
}
// CarMain
public class CarMain {
public static void main(String[] args) {
Car suv = new HyundaiCar();
//
suv.move();
suv.alarm();
}
}
AbstractCar move
Hyundai alarm
Interface Car : 구현해야할 메서드 정의
AbstractCar 추상 클래스 : Car 인터페이스 구현 및 공통 기능 기본 구현 ➡️ Interface를 구현하는 다른 클래스에서 필요한 기능만 overridng 하면 됨
HyundaiCar 구체 클래스 : AbstractCar를 상속받아 필요한 메서드만 오버라이드
골격 구현은 템플릿 메서드 패턴의 확장된 개념!
Skeletal Implementation도 상속을 가정하므로, 문서화를 해야 한다.
💨 향후 확장 포인트
Collection 프레임워크의 AbstractColletion에 대해 알아보자
🤖 최종 결론
인터페이스를 먼저 사용하고, 공통 기능이 필요하면 Skeletal Implementation을 활용하자!
❗어려웠던 점
Skeletal Implementation과 추상 메서드, 인터페이스의 개념이 겹쳐 혼동되었다.
😶🌫️ 느낀점
인터페이스를 잘 활용해 확장성과 재사용성을 높여야겠다는 생각이 들었다.
코드를 설계할 때 항상 향후 유지보수를 중요하게 고려해야 하며, 확장성을 높이는 방향으로 구현해야 한다.
Last updated