Menu
Coddy logo textTech

Абстрактные классы vs Интерфейсы

Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 38 из 87.

Теперь, когда вы понимаете как абстрактные классы, так и интерфейсы, возникает распространенный вопрос: когда следует использовать одно вместо другого? Выбор зависит от того, что именно вы пытаетесь смоделировать.

Используйте абстрактный класс, когда классы имеют общую базу с общим состоянием или поведением. Абстрактные классы могут иметь переменные экземпляра, конструкторы и сочетание абстрактных и конкретных методов:

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 (для абстрактных методов)

Практическая рекомендация: если вы обнаружите, что создаете абстрактный класс только с абстрактными методами и без состояния, интерфейс, скорее всего, будет лучшим выбором.

challenge icon

Задание

Легко

Давайте создадим систему транспортных средств, которая продемонстрирует, когда использовать абстрактные классы, а когда — интерфейсы. Вы будете моделировать транспортные средства, которые разделяют общее состояние и поведение через абстрактный класс, добавляя при этом дополнительные возможности через интерфейсы.

Вы организуете свой код в пяти файлах:

  • Vehicle.java: Создайте абстрактный класс, который послужит основой для всех транспортных средств. У каждого транспортного средства есть поле brand (String) и поле year (int). Включите конструктор для инициализации обоих полей, методы-геттеры для каждого из них и абстрактный метод startEngine(), который возвращает String. Также добавьте конкретный метод getInfo(), который возвращает: [brand] ([year]). Это идеальный случай использования абстрактного класса — транспортные средства разделяют состояние (brand, year) и некоторое поведение (getInfo), но каждое запускает двигатель по-своему.
  • Convertible.java: Определите интерфейс для транспортных средств, которые могут трансформировать свою крышу. Эта возможность не связана с тем, чем транспортное средство является — это то, что определенные транспортные средства могут делать. Объявите два метода: 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: Соберите все вместе, чтобы продемонстрировать разницу между абстрактными классами и интерфейсами. Вы получите четыре входных значения: марку автомобиля, год выпуска автомобиля, марку мотоцикла и год выпуска мотоцикла.

    Создайте Car (с 4 дверями) и Motorcycle (без коляски). Сначала продемонстрируйте общее поведение абстрактного класса, выведя getInfo() для обоих транспортных средств — они оба наследуют это от Vehicle. Затем выведите результат startEngine() для каждого — обратите внимание, как каждый тип транспортного средства реализует это по-разному.

    Наконец, продемонстрируйте возможности интерфейса: так как только Car реализует Convertible, вызовите и выведите openRoof(), а затем closeRoof() только для автомобиля.

Вы получите четыре входных значения: марку автомобиля (String), год выпуска автомобиля (int), марку мотоцикла (String) и год выпуска мотоцикла (int).

Ваш вывод должен содержать всего шесть строк. Обратите внимание, как оба транспортных средства разделяют состояние абстрактного класса и метод getInfo(), но только Car обладает возможностями кабриолета — это иллюстрирует, когда использовать каждый из подходов!

Шпаргалка

Используйте абстрактный класс, когда классы имеют общую базу с общим состоянием или поведением. Абстрактные классы могут иметь переменные экземпляра, конструкторы и сочетание абстрактных и конкретных методов:

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 (для абстрактных методов)

Рекомендация: Если вы создаете абстрактный класс только с абстрактными методами и без состояния, интерфейс, скорее всего, будет лучшим выбором.

Попробуйте сами

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: Создать Car с 4 дверями
        
        // TODO: Создать Motorcycle без коляски (false)
        
        // TODO: Вывести getInfo() для обоих транспортных средств (демонстрирует поведение общего абстрактного класса)
        
        // TODO: Вывести startEngine() для обоих транспортных средств (демонстрирует различные реализации)
        
        // TODO: Вывести openRoof() и closeRoof() только для автомобиля (демонстрирует возможности интерфейса)
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование