Когда последний раз писал абстрактный класс?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование абстрактных классов в практике
Это хороший вопрос на собеседовании — показывает, понимает ли кандидат разницу между абстрактными классами и интерфейсами, и когда применять каждый из них.
Когда писать абстрактный класс
1. Иерархия классов с общей реализацией
Абстрактный класс используется когда нужно предоставить общую реализацию для подклассов:
// Пример: система обработки платежей
public abstract class PaymentProcessor {
// Общая логика для всех процессоров
protected String merchantId;
protected String apiKey;
// Абстрактный метод — подклассы должны реализовать
public abstract TransactionResult process(Payment payment);
// Общая реализация логирования
public void logTransaction(Payment payment, TransactionResult result) {
System.out.println("Transaction: " + payment.getId() +
" Status: " + result.getStatus());
}
// Общая реализация валидации
protected void validatePayment(Payment payment) {
if (payment.getAmount() <= 0) {
throw new IllegalArgumentException("Invalid amount");
}
}
}
public class StripeProcessor extends PaymentProcessor {
@Override
public TransactionResult process(Payment payment) {
validatePayment(payment);
// Специфичная для Stripe логика
TransactionResult result = callStripeAPI(payment);
logTransaction(payment, result);
return result;
}
}
public class PayPalProcessor extends PaymentProcessor {
@Override
public TransactionResult process(Payment payment) {
validatePayment(payment);
// Специфичная для PayPal логика
TransactionResult result = callPayPalAPI(payment);
logTransaction(payment, result);
return result;
}
}
2. Состояние (protected поля)
Абстрактные классы могут иметь состояние с защищенным доступом:
public abstract class DatabaseRepository<T> {
// Защищённое состояние, доступное подклассам
protected DataSource dataSource;
protected String tableName;
protected Logger logger = LoggerFactory.getLogger(this.getClass());
public DatabaseRepository(DataSource dataSource, String tableName) {
this.dataSource = dataSource;
this.tableName = tableName;
}
// Общая логика подключения
protected Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
// Абстрактный метод
public abstract T findById(Long id);
// Общая логика логирования
protected void log(String message) {
logger.info("[" + tableName + "] " + message);
}
}
public class UserRepository extends DatabaseRepository<User> {
public UserRepository(DataSource dataSource) {
super(dataSource, "users");
}
@Override
public User findById(Long id) {
log("Finding user with id: " + id); // Используем protected метод
// Реализация с использованием protected dataSource и tableName
return null;
}
}
3. Контроль видимости методов
Абстрактный класс позволяет определить protected и private методы, недоступные через интерфейс:
public abstract class OrderProcessor {
// Публичный контракт
public abstract void processOrder(Order order);
// Protected вспомогательный метод для подклассов
protected void saveOrder(Order order) {
// Реализация
}
// Приватный метод — полностью скрыт
private void notifyWarehouse(Order order) {
// Реализация
}
}
public class StandardOrderProcessor extends OrderProcessor {
@Override
public void processOrder(Order order) {
// Может использовать protected метод
saveOrder(order);
// Не может использовать private notifyWarehouse()
}
}
// Через интерфейс нельзя выразить такие отношения доступа
public interface OrderProcessor {
void processOrder(Order order); // Всё остальное недоступно
}
4. Неабстрактные методы (конкретные реализации)
Абстрактный класс может содержать полностью реализованные методы:
public abstract class HttpClient {
protected abstract String makeRequest(String url);
// Конкретный метод — реализация готова
public String get(String url) {
String response = makeRequest(url);
if (response == null) {
throw new RuntimeException("Response is null");
}
return response;
}
public String getJson(String url) {
return get(url); // Переиспользуем логику
}
}
public class RestClient extends HttpClient {
@Override
protected String makeRequest(String url) {
// Специфичная реализация для REST
return null;
}
// Наследуем готовые методы get() и getJson()
}
Недавный пример в реальном проекте
// Система обработки документов
public abstract class DocumentProcessor {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
public final ProcessingResult process(Document document) {
// Template Method Pattern
validateDocument(document);
ProcessingResult result = processDocument(document);
saveResult(document, result);
return result;
}
// Абстрактный метод — подклассы реализуют
protected abstract ProcessingResult processDocument(Document document);
// Общая валидация
protected void validateDocument(Document document) {
if (document == null || document.getContent().isEmpty()) {
throw new IllegalArgumentException("Invalid document");
}
}
// Общее сохранение
protected void saveResult(Document document, ProcessingResult result) {
logger.info("Processed document: " + document.getId());
}
}
public class PDFProcessor extends DocumentProcessor {
@Override
protected ProcessingResult processDocument(Document document) {
// Специфичная для PDF логика
return new ProcessingResult();
}
}
public class ImageProcessor extends DocumentProcessor {
@Override
protected ProcessingResult processDocument(Document document) {
// Специфичная для изображений логика
return new ProcessingResult();
}
}
Абстрактный класс vs Интерфейс
| Критерий | Абстрактный класс | Интерфейс |
|---|---|---|
| Состояние | Может иметь protected поля | Только констант (Java 8+) |
| Конкретные методы | Да, с любой логикой | Да, но default методы (Java 8+) |
| Наследование | Один класс | Множественное |
| Когда использовать | Иерархия с общей реализацией | Контракт / Interface Segregation |
| Доступ | protected, private, public | public только |
Современный подход
В современной Java часто предпочитают интерфейсы с default методами:
// Вместо абстрактного класса
public interface PaymentProcessor {
TransactionResult process(Payment payment);
// Default реализация (Java 8+)
default void logTransaction(Payment payment, TransactionResult result) {
System.out.println("Transaction: " + payment.getId());
}
default void validatePayment(Payment payment) {
if (payment.getAmount() <= 0) {
throw new IllegalArgumentException("Invalid amount");
}
}
}
public class StripeProcessor implements PaymentProcessor {
@Override
public TransactionResult process(Payment payment) {
validatePayment(payment);
// Реализация
return null;
}
}
Таким образом, абстрактные классы используются когда нужна общая реализация, protected состояние, или контроль видимости методов. В современной Java часто достаточно интерфейсов с default методами для большинства сценариев, но абстрактные классы остаются ценным инструментом для построения иерархий классов.