본문 바로가기
Study/Java

[Java] abstract 클래스와 interface는 언제 사용할까?

by 검프 2021. 4. 4.

용어정리

우선 간단히 용어를 살펴보기로 해요.

abstract 클래스( 미완성 클래스 )

추상메서드가 없거나, 하나 이상을 포함하는 클래스에요.(추상 메서드가 하나 이상 포함될 시, 클래스 앞에 abstract를 반드시 표기해야해요)

인스턴스화가 불가능 하지만, 서브 클래싱은 가능해요.

abstract class Car {

}

class BumperCar extends Car {

}

public static void main(String[] args) {
        Car car = new Car();       // x
        Car car2 = new BumperCar();    // o
}

interface

interface는 자바에서 한 단계 더 높은 추상화를 하기 위해 사용돼요.

추상 메소드와 상수, 디폴트 메소드로 이루어진 클래스에요.

인스턴스화가 불가능 하지만, 클래스로 구현하거나, 다른 인터페이스로 확장 가능해요.

디폴트 메소드가 추가됨에 따라 인터페이스 메소드에서 바디를 가질 수 있어요.

public interface List<E> extends Collection<E> {
    int size();

        default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
}

상속

https://livenow14.tistory.com/33

상속과 관련된 부분은 위에 정리했어요.

상속을 쉽게 말하면,

상속은 클래스의 행동을 확장(extend)하는 것이 아니라 정제(refine)할 때사용해요.

확장이란, 새로운 행동을 덧붙여 기존의 행동을 부분적으로 보완하는 것을 의미하고,

정제란, 부분적으로 불완전한 행동을 완전하게 만드는 것을 의미합니다.

 

일단 인터페이스로 추상화하자.

웬만한 경우는 인터페이스로 추상화 하는 것이 좋아요.

하지만 이럴 경우 중복이 생기는 경우가 있어요. 이와 관련된 질문과 답변을 남겨봐요.

인터페이스와 추상클래스 관련 질문

현재 Car가 하나만 정의되어있는데, 
인터페이스의 구현체인 SonataCar, AvanTeCar, porshecCar 등의 객체가 생길때 
move를 위한 로직이 중복될거 같아요.
중복코드를 인터페이스를 사용하여 어떻게 줄일 수 있을까요? 

답변

인터페이스 - 추상클래스 - 클래스 이런 구조로 중복을 제거 한다. 
인터페이스의 디폴트 메서드는 → 정말 필요할 때만 사용하자
부모클래스의 인스턴스 변수를 전부 private를 만들어라. 
재정의하지 않는 메서드는 final화 하라

즉, 인터페이스로 추상화가 이루어 졌는데, 이를 구현하는 구현체들의 중복이 많을 때, 추상클래스의 공통의 멤버변수와 공통의 바디를 같는 메소드를 통해 중복을 제거할 수 있어요.

이때 사용하는 추상클래스는 정제를 위해 사용되기 때문에 상속의 좋은 예라 볼 수 있어요.

인터페이스의 디폴트 메소드는 정말 필요할때만 사용하는 것이기에 남발하지 않는 것이 좋아요.

인터페이스 - 추상클래스 - 클래스의 예

package study;

import org.junit.jupiter.api.Test;

public class StudyTest {
    @Test
    void superTest() {
        Move avanteCar = new AvanteCar();
        Move porscheCar = new PorscheCar();
        avanteCar.go();
        porscheCar.go();

        System.out.println("avanteCar = " + avanteCar.getDistance());
        System.out.println("porscheCar.getDistance() = " + porscheCar.getDistance());
    }

}

interface Move {
    void go();

    void back();

    int getDistance();
}

abstract class Car implements Move {
    private int distance = 0;

    @Override
    public final int getDistance() {
        return this.distance;
    }

    @Override
    public final void go() {
        distance += getGoCount();
    }

    @Override
    public final void back() {
        distance -= getBackCount();
    }

    protected abstract int getGoCount();

    protected abstract int getBackCount();
}

class AvanteCar extends Car {
    private static final int GO_COUNT = 1;
    private static final int BACK_COUNT = 3;

    @Override
    protected int getGoCount() {
        return GO_COUNT;
    }

    @Override
    protected int getBackCount() {
        return BACK_COUNT;
    }
}

class PorscheCar extends Car {
    private static final int GO_COUNT = 100;
    private static final int BACK_COUNT = 2;

    @Override
    protected int getGoCount() {
        return GO_COUNT;
    }

    @Override
    protected int getBackCount() {
        return BACK_COUNT;
    }
}

 

결론

추상화는 인터페이스로 시작헤요.

추상화가 필요할 때, 우선적으로 인터페이스를 고려해봐요.

중복이 생긴다면 인터페이스 - 추상클래스 - 클래스의 구조를 고려해요.

추상클래스와 인터페이스를 따로 보지 않고, 함꼐 쓰면 더 좋다고 생각 하기로 해요.

 

Refer

댓글