Ответ
Да, наследование применялось осознанно, преимущественно для создания строгих иерархий типов и повторного использования кода в случаях отношения «является» (is-a).
Типичный use case: Абстрактный базовый класс, определяющий общий контракт и реализацию для семейства связанных сущностей.
Пример: Иерархия процессоров оплаты
// Абстрактный класс задает общий алгоритм (шаблонный метод) и контракт.
public abstract class PaymentProcessor {
// Общая реализация для всех процессоров
public final void executePayment(double amount) {
if (validate()) {
process(amount);
logTransaction();
} else {
throw new ValidationException("Payment validation failed");
}
}
// Абстрактные методы, которые должны реализовать потомки
protected abstract boolean validate();
protected abstract void process(double amount);
// Общий метод с реализацией по умолчанию
protected void logTransaction() {
System.out.println("Transaction logged");
}
}
// Конкретная реализация для кредитных карт
public class CreditCardProcessor extends PaymentProcessor {
@Override
protected boolean validate() {
// Специфичная логика проверки карты (CVV, срок действия)
return true;
}
@Override
protected void process(double amount) {
// Логика списания средств с карты через платежный шлюз
System.out.println("Processing credit card payment: " + amount);
}
}
Почему наследование, а не композиция? В данном случае наследование уместно, так как CreditCardProcessor является конкретным видом PaymentProcessor и должен следовать его фиксированному алгоритму (executePayment). Для более гибкого повторного использования поведения часто предпочитается композиция.