Абстрактные классы 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-методы и не имеющий состояния, вероятно, лучше выбрать интерфейс.
Задание
ЛегкоДавайте создадим систему транспортных средств, которая демонстрирует, когда следует использовать абстрактные классы, а когда — интерфейсы. Вы смоделируете транспортные средства, которые используют общее состояние и поведение через абстрактный класс, добавляя при этом необязательные возможности через интерфейсы.
Вы организуете код в пяти файлах:
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 класс, который extendsVehicleи implementsConvertible. У Car есть дополнительное полеnumDoorsтипа int. Используйтеsuper, чтобы инициализировать унаследованные поля. РеализуйтеstartEngine(), чтобы он возвращал:[brand] car engine started. РеализуйтеopenRoof(), чтобы он возвращал:[brand] roof opening, аcloseRoof()— чтобы он возвращал:[brand] roof closing.Motorcycle.java: Create класс, который extendsVehicle, но does NOT implementConvertible: у мотоциклов нет крыш! У 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() только для автомобиля (демонстрирует возможности интерфейса)
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово thisМетодыПоля (атрибуты)Метод-конструкторПерегрузка конструкторовИтоги — Простой калькулятор4Наследование
Основы наследования (extends)Ключевое слово superПереопределение методов (@Override)Цепочка конструкторовКласс ObjectОдиночное и многоуровневое наследованиеПочему нет множественного наследования классовИтоги — Иерархия сотрудников7Специальные методы и класс Object
Метод toString()equals() и hashCode()Метод clone()compareTo() и ComparableИнтерфейс ComparatorПовторение — Кастомная сортировка2Модификаторы доступа и инкапсуляция
Обзор уровней доступаМетоды геттеры и сеттерыСокрытие данныхКлючевое слово finalИтоги — Менеджер банковского счета5Полиморфизм
Основы перегрузки методовПереопределение методов (Run-Time)Upcasting и DowncastingОператор instanceofАбстрактные классы и методыИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияАгрегация против композицииВнутренние, вложенные и анонимные классыEnums и методы перечисленийRecords (Java 16+)Sealed-классы (Java 17+)11Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy3Свойства класса и статические члены
Переменные экземпляра и статические переменныеСтатические методыСтатические блокиКонстанты (static final)Итоги: Счетчик и утилиты6Интерфейсы и абстрактные классы
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовDefault и Static методы в интерфейсахАбстрактные классы vs ИнтерфейсыФункциональные интерфейсыИтоги — Платежная система9Обобщения
Введение в обобщенияОбобщенные классыОбобщенные методыОграниченные параметры типаСимволы подстановки (Wildcards: ?, extends, super)Итоги — Обобщенный контейнер12Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»Потренируйтесь самостоятельно: Онлайн-компилятор Java