Примените принципы SOLID к проекту простого сервиса бронирования отелей: опишите конкретные нарушения при монолитном классе BookingManager и предложите рефакторинг с объяснением, какие принципы за что отвечают

6 Окт в 10:39
7 +1
0
Ответы
1

Коротко: SOLID — это набор принципов проектирования, который помогает сделать код гибким, расширяемым и тестируемым. Ниже покажу — на конкретных примерах — какие нарушения типично встречаются в монолитном классе BookingManager и как его рефакторить, чтобы каждый принцип SOLID был соблюдён.

1) Пример «монолита» — что чаще всего встречается
(псевдокод / C#-стиль для ясности):

class BookingManager
{
// создаёт бронь, проверяет доступность, рассчитывает цену,
// проводит платёж, сохраняет в БД, отправляет уведомления, логирует, обрабатывает отмены и т.п.
public BookingResult CreateBookingBookingRequestreqBookingRequest reqBookingRequestreq {
// 1. Валидация входных данных
if !Validate(req)!Validate(req)!Validate(req) throw new ValidationException;

// 2. Проверка доступности номеров
if !CheckAvailability(req.RoomType,req.Dates)!CheckAvailability(req.RoomType, req.Dates)!CheckAvailability(req.RoomType,req.Dates) return fail;
// 3. Расчёт цены с учётом скидок
var price = CalculatePricereqreqreq;
// 4. Платёж
var paymentResult = ProcessPaymentreq.PaymentDetails,pricereq.PaymentDetails, pricereq.PaymentDetails,price;
if !paymentResult.Success!paymentResult.Success!paymentResult.Success return fail;
// 5. Сохранение записи в БД
SaveBookingToDb.........;
// 6. Отправка уведомления клиенту
SendEmail.........;
SendSms.........;
// 7. Логирование/метрики
Log.........;
return success;
}
// плюс методы CancelBooking, Refund, GetBookings, ImportRatePlan, etc.

}

Какие проблемы здесь:

один класс делает всё: доступность, расчёт цены, оплата, persist, уведомления, валидация, логирование, интеграции — нарушение SRP;чтобы добавить новый способ оплаты или уведомления, придётся менять BookingManager — нарушение OCP;интерфейс/класс, который обязан реализовать много методов, которые конкретному клиенту не нужны — нарушение ISP впримереэтопроявляется,еслимывынесеминтерфейсIBookingManagerскучейметодовв примере это проявляется, если мы вынесем интерфейс IBookingManager с кучей методоввпримереэтопроявляется,еслимывынесеминтерфейсIBookingManagerскучейметодов;если наследовать BookingManager для особой логики и ломать контракт — потенциальное нарушение LSP;BookingManager напрямую использует реализацию БД/платежного шлюза — нарушение DIP зависитотдеталейзависит от деталейзависитотдеталей.

2) Как рефакторить — целевые сущности и интерфейсы
Основная идея: разбить ответственность, ввести абстракции интерфейсыинтерфейсыинтерфейсы, внедрять зависимости DIDIDI и расширять поведение через стратегии/плагины/цепочки.

Предлагаемая структура строгоеразделениеролейстрогое разделение ролейстрогоеразделениеролей:

Domain model:

Booking, Customer, Room, Payment, Price, BookingRequest, BookingResult

Core services / интерфейсы:

IBookingService — высокий уровень для создания/отмены броней координаторкоординаторкоординатор.IAvailabilityChecker — проверка доступности номеров.IPricingStrategy илиIPricingServiceили IPricingServiceилиIPricingService — расчёт цены поддерживаетплагины:скидки,налогиподдерживает плагины: скидки, налогиподдерживаетплагины:скидки,налоги.IPaymentProcessor — обработка платежей интерфейс,разныереализациидляStripe,PayPal,testинтерфейс, разные реализации для Stripe, PayPal, testинтерфейс,разныереализациидляStripe,PayPal,test.IBookingRepository — абстракция доступа к данным Save,Get,QuerySave, Get, QuerySave,Get,Query.INotificationSender илинесколько:IEmailSender,ISmsSenderили несколько: IEmailSender, ISmsSenderилинесколько:IEmailSender,ISmsSender — отправка уведомлений.IBookingValidator илинаборвалидаторовили набор валидаторовилинаборвалидаторов — валидация запроса.ILogger / IEventPublisher — логирование и публикация доменных событий.

Patterns:

Strategy: для расчёта цены, для оплаты, для уведомлений.Chain of Responsibility / Composite: для набора валидаторов или правил ценообразования.Unit of Work / Transaction + Domain Events: уведомления и интеграции после успешной записи.

Пример интерфейсов псевдокодпсевдокодпсевдокод:

interface IAvailabilityChecker { bool IsAvailableRoomType,DateRangeRoomType, DateRangeRoomType,DateRange; }
interface IPricingService { Money CalculateBookingRequestBookingRequestBookingRequest; }
interface IPaymentProcessor { PaymentResult ChargePaymentDetails,MoneyPaymentDetails, MoneyPaymentDetails,Money; }
interface IBookingRepository { void SaveBookingBookingBooking; Booking Getididid; }
interface INotificationSender { void SendBookingNotificationBookingNotificationBookingNotification; }
interface IBookingValidator { ValidationResult ValidateBookingRequestBookingRequestBookingRequest; }
interface IBookingService { BookingResult CreateBookingBookingRequestBookingRequestBookingRequest; }

Теперь IBookingService реализует координацию, не детали:

class BookingService : IBookingService
{
private readonly IAvailabilityChecker availability;
private readonly IPricingService pricing;
private readonly IPaymentProcessor payment;
private readonly IBookingRepository repo;
private readonly IEnumerable notifiers;
private readonly IBookingValidator validator;
private readonly ILogger log;

public BookingResult CreateBookingBookingRequestreqBookingRequest reqBookingRequestreq {
var v = validator.Validatereqreqreq;
if !v.Ok!v.Ok!v.Ok return fail;
if !availability.IsAvailable(req.RoomType,req.Dates)!availability.IsAvailable(req.RoomType, req.Dates)!availability.IsAvailable(req.RoomType,req.Dates) return fail;
var price = pricing.Calculatereqreqreq;
var paymentResult = payment.Chargereq.Payment,pricereq.Payment, pricereq.Payment,price;
if !paymentResult.Success!paymentResult.Success!paymentResult.Success return fail;
var booking = new Booking.........;
repo.Savebookingbookingbooking;
// публикация события/уведомления после успешной транзакции
foreach varninnotifiersvar n in notifiersvarninnotifiers n.SendnewBookingCreated(booking)new BookingCreated(booking)newBookingCreated(booking);
log.Info.........;
return success;
}

}

3) Как SOLID принципы применяются и какие проблемы решают

SRP SingleResponsibilityPrincipleSingle Responsibility PrincipleSingleResponsibilityPrinciple

Проблема в монолите: BookingManager отвечает за 8+ разных вещей.Рефакторинг: выделить отдельные классы для каждой ответственности: AvailabilityChecker, PricingService, PaymentProcessor, Repository, NotificationSender, Validator.Результат: проще менять/тестировать/реиспользовать каждую часть.

OCP Open/ClosedPrincipleOpen/Closed PrincipleOpen/ClosedPrinciple

Проблема в монолите: чтобы добавить новый способ оплаты или новую акцию, надо править BookingManager.Рефакторинг: ввести абстракции IPaymentProcessor,IPricingService,INotificationSenderIPaymentProcessor, IPricingService, INotificationSenderIPaymentProcessor,IPricingService,INotificationSender и регистрировать новые реализации. Добавление новых стратегий не требует изменения BookingService.Результат: расширяемость через новые реализации, минимальные изменения уже работающего кода.

LSP LiskovSubstitutionPrincipleLiskov Substitution PrincipleLiskovSubstitutionPrinciple

Потенциальная проблема: если создают подкласс BookingManagerSpecial и меняют семантику CreateBooking например,бросаютexceptionsтамгдебазовыйвозвращалкоднапример, бросают exceptions там где базовый возвращал коднапример,бросаютexceptionsтамгдебазовыйвозвращалкод, то клиенты ломаются.Рефакторинг: проектировать абстракции с ясными контрактами чтовозвращается,какиеисключениядопустимычто возвращается, какие исключения допустимычтовозвращается,какиеисключениядопустимы. Проверяем, что любые реализации интерфейса соблюдают контракт: same input -> compatible output/side-effects.Результат: можно безопасно заменять реализации напримертестовыйплатежныйпроцессорвместобоевогонапример тестовый платежный процессор вместо боевогонапримертестовыйплатежныйпроцессорвместобоевого.

ISP InterfaceSegregationPrincipleInterface Segregation PrincipleInterfaceSegregationPrinciple

Проблема: один «толстый» интерфейс IBookingManager с множеством методов заставляет клиентов реализовывать/зависеть от ненужных методов.Рефакторинг: разбить интерфейсы на маленькие: IBookingService, IBookingRepository, IPaymentProcessor, IAvailabilityChecker, INotificationSender. Клиентам даём только те интерфейсы, которые им нужны.Результат: уменьшение связности и более понятные контрактные границы.

DIP DependencyInversionPrincipleDependency Inversion PrincipleDependencyInversionPrinciple

Проблема: BookingManager напрямую создаёт/использует конкретные классы SqlRepository,StripeClient,SmtpClientSqlRepository, StripeClient, SmtpClientSqlRepository,StripeClient,SmtpClient.Рефакторинг: BookingService зависит от абстракций интерфейсовинтерфейсовинтерфейсов. Конкретные реализации внедряются извне через DI-контейнер или фабрики.Результат: проще тестировать мокимокимоки, менять реализации, конфигурировать систему.

4) Небольшие практические рекомендации по рефакторингу инкрементальноинкрементальноинкрементально

Шаг 1: Написать тесты на текущую логику CreateBooking чтобыповедениесохраненочтобы поведение сохраненочтобыповедениесохранено.Шаг 2: Выделить IAvailabilityChecker и перенести логику проверки доступности; внедрить в BookingManager иливременныйфасадили временный фасадиливременныйфасад через конструктор. Запустить тесты.Шаг 3: Выделить IPricingService первоначальноимплементациякопируетстаруюлогикупервоначально имплементация копирует старую логикупервоначальноимплементациякопируетстаруюлогику. Тесты.Шаг 4: Выделить IPaymentProcessor внедрятьтестовыйstub/моквовремятестоввнедрять тестовый stub/мок во время тестоввнедрятьтестовыйstub/моквовремятестов.Шаг 5: Выделить IBookingRepository persistpersistpersist. Ввести транзакцию/UnitOfWork, чтобы уведомления отправлялись только после успешной коммита.Шаг 6: Создать BookingService как координирующий класс high−levelhigh-levelhighlevel, удалить логику из монолитного BookingManager. Монолит можно оставить как адаптер к новому BookingService, чтобы не ломать внешний API, и постепенно удалить.Шаг 7: Вынести уведомления и логирование, подключать через события/observer.

5) Примеры шаблонов/вариантов

Pricing: реализовать как цепочку правил ChainofResponsibilityChain of ResponsibilityChainofResponsibility или набор стратегий, которые комбинируются.Payment: Strategy для разных провайдеров + Factory для выбора по метаданным.Validation: набор IValidator реализует проверку; BookingService просто прогоняет все валидаторы.Events: после сохранения публикуем BookingCreated event, подписчики нотсифай,аналитиканотсифай, аналитиканотсифай,аналитика реагируют.

6) Контроль ошибок и атомарность

Используйте транзакции/UnitOfWork: сначала сохраняем бронь, затем отправляем уведомления только после подтверждённого коммита.Для взаимодействия с внешними сервисами платёж,уведомленияплатёж, уведомленияплатёж,уведомления применяйте стратегию компенсации/ре.try илиsagaдляраспределённыхпроцессовили saga для распределённых процессовилиsagaдляраспределённыхпроцессов.

7) Маппинг нарушений → рефакторинг краткократкократко

SRP: выделить Availability, Pricing, Payment, Repo, Notification, Validation.OCP: ввести IPricingService / IPaymentProcessor / INotificationSender — добавляем реализации без изменения BookingService.LSP: чёткие контракты интерфейсов, предусмотреть поведение в пограничных сценариях невозвращатьnull,использоватьResult−объектыне возвращать null, использовать Result-объектыневозвращатьnull,использоватьResultобъекты.ISP: дробить большие интерфейсы на специализированные.DIP: внедрять зависимости через интерфейсы, использовать DI-контейнер.

8) Заключение — что вы получаете

Меньше «больших классов» — легче понимать и тестировать.Локализация изменений: добавить платежный провайдер или акцию — минимальные изменения.Проще писать unit-тесты можномокатьIPaymentProcessor,IBookingRepositoryможно мокать IPaymentProcessor, IBookingRepositoryможномокатьIPaymentProcessor,IBookingRepository.Система становится более гибкой и готовой к масштабированию.

Если хотите, могу:

предложить конкретную структуру файлов/пакетов для проекта;привести полный пример кода (на C# / Java / TypeScript) с интерфейсами и базовыми реализациями;показать, как организовать миграцию существующего монолита пошагово с примерами тестов.
6 Окт в 11:19
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир