Menu
Coddy logo textTech

추상 클래스 vs 인터페이스

Coddy Java 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 87개 중 38번째.

이제 추상 클래스와 인터페이스를 모두 이해했으므로, 한 가지 흔한 질문이 생깁니다. 언제 하나를 다른 것보다 우선해서 사용해야 할까요? 선택은 무엇을 모델링하려는지에 따라 달라집니다.

클래스들이 공유된 상태나 동작을 갖는 공통 기반을 공유할 때 추상 클래스를 사용하세요. 추상 클래스는 인스턴스 변수, 생성자, 그리고 추상 메서드와 구체 메서드의 혼합을 가질 수 있습니다:

public abstract class Animal {
    protected String name;  // 공유된 상태
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void sleep() {  // 공유된 동작
        System.out.println(name + " is sleeping");
    }
    
    public abstract void makeSound();  // 반드시 구현되어야 함
}

서로 관련이 없는 클래스들이 공유할 수 있는 기능을 정의하고 싶을 때 인터페이스를 사용하세요. 인터페이스는 객체가 무엇인지가 아니라, 객체가 무엇을 할 수 있는지에 초점을 맞춥니다:

public interface Flyable {
    void fly();
}

// 관련 없는 클래스들이 이 기능을 공유할 수 있습니다
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }

간단한 비교는 다음과 같습니다:

특징추상 클래스인터페이스
인스턴스 변수상수만 가능
생성자아니요
다중 상속아니요 (단일 extends)예 (다중 implements)
접근 제어자제한 없음Public만 가능 (추상 메서드의 경우)

실질적인 가이드라인: 만약 추상 메서드만 있고 상태(state)가 없는 추상 클래스를 만들고 있다면, 인터페이스가 더 나은 선택일 가능성이 높습니다.

challenge icon

챌린지

쉬움

추상 클래스와 인터페이스를 언제 사용해야 하는지 보여주는 차량 시스템을 구축해 보겠습니다. 추상 클래스를 통해 공통된 상태와 동작을 공유하는 차량 모델을 만들고, 인터페이스를 통해 선택적인 기능을 추가할 것입니다.

코드는 다음 다섯 개의 파일로 구성됩니다:

  • Vehicle.java: 모든 차량의 기초가 되는 추상 클래스를 생성합니다. 모든 차량은 brand 필드(String)와 year 필드(int)를 가집니다. 두 필드를 초기화하는 생성자, 각 필드에 대한 getter 메서드, 그리고 String을 반환하는 추상 메서드 startEngine()을 포함하세요. 또한 [brand] ([year])를 반환하는 구체 메서드 getInfo()를 추가하세요. 이는 추상 클래스의 완벽한 사용 사례입니다. 차량들은 상태(brand, year)와 일부 동작(getInfo)을 공유하지만, 엔진을 시동하는 방식은 각기 다르기 때문입니다.
  • Convertible.java: 지붕을 변환할 수 있는 차량을 위한 인터페이스를 정의합니다. 이 기능은 차량이 무엇인지(is)와는 관련이 없으며, 특정 차량이 할 수 있는(can do) 일입니다. 두 개의 메서드 openRoof()closeRoof()를 선언하고, 둘 다 String을 반환하도록 하세요.
  • Car.java: Vehicle을 상속받고 Convertible을 구현하는 클래스를 생성합니다. Car는 추가적인 numDoors 필드(int)를 가집니다. 상속받은 필드를 초기화하기 위해 super를 사용하세요. startEngine()[brand] car engine started를 반환하도록 구현하세요. openRoof()[brand] roof opening을, closeRoof()[brand] roof closing을 반환하도록 구현하세요.
  • Motorcycle.java: Vehicle을 상속받지만 Convertible은 구현하지 않는 클래스를 생성합니다. 오토바이는 지붕이 없기 때문입니다! Motorcycle은 hasSidecar 필드(boolean)를 가집니다. startEngine()[brand] motorcycle engine roaring을 반환하도록 구현하세요.
  • Main.java: 추상 클래스와 인터페이스의 차이점을 보여주기 위해 모든 것을 하나로 모읍니다. 자동차 브랜드, 자동차 연식, 오토바이 브랜드, 오토바이 연식의 네 가지 입력을 받게 됩니다.

    자동차(문 4개)와 오토바이(사이드카 없음)를 생성하세요. 먼저, 두 차량 모두에 대해 getInfo()를 출력하여 공유된 추상 클래스의 동작을 보여주세요. 두 차량 모두 Vehicle로부터 이를 상속받았습니다. 그런 다음 각 차량의 startEngine() 결과를 출력하세요. 각 차량 유형이 이를 어떻게 다르게 구현하는지 확인해 보세요.

    마지막으로 인터페이스 기능을 보여주세요. Car만 Convertible을 구현하므로, 자동차에 대해서만 openRoof()를 호출하여 출력한 뒤 closeRoof()를 호출하여 출력하세요.

자동차 브랜드(String), 자동차 연식(int), 오토바이 브랜드(String), 오토바이 연식(int)의 네 가지 입력을 받습니다.

출력은 총 6줄이어야 합니다. 두 차량이 어떻게 추상 클래스의 상태와 getInfo() 메서드를 공유하는지, 그리고 왜 Car만 컨버터블 기능을 가지는지 주목해 보세요. 이는 각 접근 방식을 언제 사용해야 하는지 잘 보여줍니다!

치트 시트

클래스들이 공유된 상태나 동작을 가진 공통 기반을 공유할 때 추상 클래스(abstract class)를 사용하세요. 추상 클래스는 인스턴스 변수, 생성자, 그리고 추상 메서드와 구체 메서드의 혼합을 가질 수 있습니다:

public abstract class Animal {
    protected String name;  // 공유된 상태
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void sleep() {  // 공유된 동작
        System.out.println(name + " is sleeping");
    }
    
    public abstract void makeSound();  // 반드시 구현되어야 함
}

서로 관련 없는 클래스들이 공유할 수 있는 기능을 정의하고 싶을 때 인터페이스(interface)를 사용하세요. 인터페이스는 객체가 무엇인지가 아니라, 객체가 무엇을 할 수 있는지에 초점을 맞춥니다:

public interface Flyable {
    void fly();
}

// 서로 관련 없는 클래스들이 이 기능을 공유할 수 있습니다
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }
특징추상 클래스인터페이스
인스턴스 변수상수만 가능
생성자아니요
다중 상속아니요 (단일 상속)예 (다중 구현)
접근 제어자모두 가능Public만 가능 (추상 메서드의 경우)

가이드라인: 상태가 없고 오직 추상 메서드만 있는 추상 클래스를 만들고 있다면, 인터페이스가 더 나은 선택일 가능성이 높습니다.

직접 해보기

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 입력 읽기
        String carBrand = scanner.nextLine();
        int carYear = scanner.nextInt();
        scanner.nextLine(); // 개행 문자 소비
        String motorcycleBrand = scanner.nextLine();
        int motorcycleYear = scanner.nextInt();
        
        // TODO: 문이 4개인 Car 생성
        
        // TODO: 사이드카가 없는(false) Motorcycle 생성
        
        // TODO: 두 차량 모두에 대해 getInfo() 출력 (공통 추상 클래스 동작 확인)
        
        // TODO: 두 차량 모두에 대해 startEngine() 출력 (서로 다른 구현 확인)
        
        // TODO: Car에 대해서만 openRoof()와 closeRoof() 출력 (인터페이스 기능 확인)
    }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨