Menu
Coddy logo textTech

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

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

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

Используй abstract class, когда classes имеют общую основу с общим состоянием или behavior. Abstract classes могут содержать переменные экземпляра, конструкторы, а также сочетание abstract и concrete методов:

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 { ... }

Вот краткое сравнение:

ФункцияAbstract ClassИнтерфейс
Переменные экземпляраДаТолько константы
КонструкторыДаНет
Множественное наследованиеНет (один extends)Да (несколько implements)
Модификаторы доступаЛюбыеТолько Public (для abstract методов)

Практическое правило: если ты создаёшь abstract-класс, содержащий только abstract-методы и не имеющий состояния, вероятно, лучше выбрать интерфейс.

challenge icon

Задание

Легко

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

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

  • Vehicle.java: Create абстрактный класс, который служит основой для всех транспортных средств. У каждого транспортного средства есть поле brand типа String и поле year типа int. Добавьте constructor для инициализации обоих полей, getter-методы для каждого из них и абстрактный метод startEngine(), возвращающий String. Также добавьте concrete-метод getInfo(), который возвращает: [brand] ([year]). Это идеальный случай для использования абстрактного класса: транспортные средства имеют общее состояние (brand, year) и некоторое общее поведение (getInfo), но каждое из них запускает двигатель по-своему.
  • Convertible.java: Define интерфейс для транспортных средств, которые могут складывать крышу. Эта capability не связана с тем, чем транспортное средство является: это то, что некоторые транспортные средства can do. Declare два метода: openRoof() и closeRoof(), оба возвращают String.
  • Car.java: Create класс, который extends Vehicle и implements Convertible. У Car есть дополнительное поле numDoors типа int. Используйте super, чтобы инициализировать унаследованные поля. Реализуйте startEngine(), чтобы он возвращал: [brand] car engine started. Реализуйте openRoof(), чтобы он возвращал: [brand] roof opening, а closeRoof() — чтобы он возвращал: [brand] roof closing.
  • Motorcycle.java: Create класс, который extends Vehicle, но does NOT implement Convertible: у мотоциклов нет крыш! У Motorcycle есть поле hasSidecar типа boolean. Реализуйте startEngine(), чтобы он возвращал: [brand] motorcycle engine roaring.
  • Main.java: Объедините всё, чтобы показать различие между абстрактными классами и интерфейсами. Вы получите четыре входных значения: марку автомобиля, год выпуска автомобиля, марку мотоцикла и год выпуска мотоцикла.

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

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

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

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

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

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Проверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор Java