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 메서드에서 활용이 가능해졌는데..?

  1. 인터페이스는 필드, 상태를 가질 수 없다 static final 상수만 선언 가능하고, 인스턴스 필드를 가질 수 없다. 변수가 필요하다면 추상 클래스가 필요!

  2. 인터페이스는 여전히 생성자를 가질 수 없다

  3. 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
  1. Interface Car : 구현해야할 메서드 정의

  2. AbstractCar 추상 클래스 : Car 인터페이스 구현 및 공통 기능 기본 구현 ➡️ Interface를 구현하는 다른 클래스에서 필요한 기능만 overridng 하면 됨

  3. HyundaiCar 구체 클래스 : AbstractCar를 상속받아 필요한 메서드만 오버라이드

  • 골격 구현은 템플릿 메서드 패턴의 확장된 개념!

  • Skeletal Implementation도 상속을 가정하므로, 문서화를 해야 한다.


💨 향후 확장 포인트

  • Collection 프레임워크의 AbstractColletion에 대해 알아보자

🤖 최종 결론

인터페이스를 먼저 사용하고, 공통 기능이 필요하면 Skeletal Implementation을 활용하자!


❗어려웠던 점

  • Skeletal Implementation과 추상 메서드, 인터페이스의 개념이 겹쳐 혼동되었다.


😶‍🌫️ 느낀점

  • 인터페이스를 잘 활용해 확장성과 재사용성을 높여야겠다는 생각이 들었다.

  • 코드를 설계할 때 항상 향후 유지보수를 중요하게 고려해야 하며, 확장성을 높이는 방향으로 구현해야 한다.

Last updated