Ответ
При проектировании классов руководствуюсь принципами SOLID и отдаю предпочтение композиции перед наследованием. Вот конкретные подходы на примере класса OrderProcessor:
- Принцип единственной ответственности (SRP): Класс отвечает только за координацию процесса обработки заказа, а не за логику оплаты или логирования.
- Принцип открытости/закрытости (OCP): Класс открыт для расширения (можно подставить любую реализацию
IPaymentGateway), но закрыт для модификации. - Принцип инверсии зависимостей (DIP): Класс зависит от абстракций (
IPaymentGateway,ILogger), а не от конкретных реализаций. - Внедрение зависимостей (DI): Зависимости передаются через конструктор, что упрощает тестирование и управление жизненным циклом объектов.
- Композиция: Функциональность собрана из независимых компонентов (шлюз оплаты, логгер), а не унаследована от базового класса.
- Инкапсуляция: Внутреннее состояние (зависимости) и детали реализации скрыты, предоставлен четкий публичный метод
ProcessOrder.
Пример реализации:
public class OrderProcessor
{
private readonly IPaymentGateway _paymentGateway;
private readonly ILogger _logger;
// Внедрение зависимостей через конструктор
public OrderProcessor(IPaymentGateway paymentGateway, ILogger logger)
{
_paymentGateway = paymentGateway ?? throw new ArgumentNullException(nameof(paymentGateway));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
// Единственная публичная ответственность класса
public async Task<ProcessResult> ProcessOrder(Order order)
{
// Валидация входных данных (Guard Clauses)
if (order == null) throw new ArgumentNullException(nameof(order));
if (order.Total <= 0) throw new InvalidOrderException("Order total must be positive.");
try
{
// Делегирование ответственности специализированным сервисам
var paymentResult = await _paymentGateway.ProcessPaymentAsync(order);
_logger.LogInformation($"Order {order.Id} processed successfully. Payment ID: {paymentResult.Id}");
return ProcessResult.Success(paymentResult.Id);
}
catch (PaymentGatewayException ex) // Ловим только специфичные исключения
{
_logger.LogError(ex, $"Failed to process payment for order {order.Id}");
// Преобразуем низкоуровневое исключение в исключение предметной области
throw new OrderProcessingException("Payment failed", ex);
}
}
}
Итог: Такой подход делает класс тестируемым (легко подменить IPaymentGateway моком), поддерживаемым и готовым к интеграции в современные DI-контейнеры.