Является ли бизнес-логика в конструкторах хорошей идеей?
В настоящее время я перестраиваю специализированную систему билетов на работу (в основном используется для поддержки людей с ошибками в оборудовании для дистанционного зондирования...). Во всяком случае, мне было интересно, является ли хорошая работа типа рабочего процесса конструктором объекта.
Например, в настоящее время это:
$ticket = new SupportTicket(
$customer,
$title,
$start_ticket_now,
$mail_customer
);
как только объект будет создан, он поместит строку в базу данных, отправит и отправит клиенту электронное письмо с подтверждением, возможно, отправит текстовое сообщение ближайшему специалисту и т.д.
Если конструктор отключит все, что работает, или что-то вроде следующего?
$ticket = new SupportTicket($customer, $title);
$customer->confirmTicketMailed($ticket);
$helpdesk->alertNewTicket($ticket);
Если это помогает, все объекты основаны на стиле ActiveRecord.
Я думаю, это может быть вопросом мнения, но как вы думаете, что лучше всего делать?
Ответы
Ответ 1
Если конструктор выполняет все, что работает, конструктор знает о многих других объектах домена. Это создает проблему зависимости. Если ticket
действительно знает о Customer
и HelpDesk
? Когда добавляются новые функции, разве не возможно, что в рабочий процесс будут добавлены новые объекты домена, и разве это не значит, что наш бедный ticket
должен знать о постоянно растущей совокупности объектов домена?
Проблема с spiderwebs такой зависимости заключается в том, что изменение исходного кода на любой объект домена повлияет на наших бедных ticket
. ticket
будет иметь столько знаний о системе, что независимо от того, что произойдет, будет задействован ticket
. Вы найдете неприятные выражения if
, которые собираются внутри этого конструктора, проверяя базу данных конфигурации, состояние сеанса и так много других вещей. ticket
вырастет, чтобы стать классом богов.
Еще одна причина, по которой мне не нравятся конструкторы, которые делают вещи, - это то, что объекты вокруг них очень трудно тестировать. Мне нравится писать много штучных объектов. Когда я пишу тест против Customer
, я хочу передать ему издеваемое ticket
. Если конструктор ticket
контролирует рабочий процесс и танцует между Customer
и другими объектами домена, то маловероятно, что я смогу высмеять его, чтобы протестировать Customer
.
Я предлагаю вам прочитать SOLID Principles, документ, который я написал несколько лет назад об управлении зависимостями в объектно-ориентированных проектах.
Ответ 2
Разделите вещи. На самом деле, вы не хотите, чтобы билет знал, как он должен отправлять электронную почту и т.д. Это работа службы, например класса.
[Обновление] Я не думаю, что предлагаемые шаблоны factory хороши для этого. A factory полезен, если вы хотите создать различные реализации билетов, не введя эту логику в сам билет (например, через перегруженные конструкторы).
Взгляните на концепцию сервиса, предложенную в проекте, управляемом доменами.
Услуги. Когда операция не является концептуально принадлежащей любому объекту. Следуя естественным контурам проблемы, вы можете реализовать эти операции в сервисах. Концепция сервиса называется "Чистая сборка" в GRASP.
Ответ 3
Проблема, с точки зрения дизайна OO, заключается не столько в том, должна ли эта функциональность быть реализована в конструкторе (в отличие от других методов этого класса), а в том, должен ли класс SupportTicket знать, как делать все эти вещи.
Вкратце, класс SupportTicket должен моделировать билет поддержки и только билет поддержки. Создание электронной почты, зная, как отправить это электронное письмо заказчику, окупить билет для обработки и т.д., - это все куски функциональности, которые вы должны переместить из класса SupportTicket и инкапсулировать в другое место. Причины этого включают низкую связь, более высокую степень сцепления и улучшенную тестируемость.
Посмотрите Принцип единой ответственности, который объясняет преимущества этого. В частности, этот блог - хорошее место для чтения на SRP и других ключевых принципах хорошего дизайна OO.
Ответ 4
Короткий ответ - нет.
В аппаратном дизайне у нас было высказывание: "Не ставьте ворота на часы или линию reset - это заслоняет логику". То же самое касается и по той же причине.
Более длинный ответ придется ждать, но см. " Screetchingly Очевидный код.
Ответ 5
На самом деле я никогда не был доволен ни одним из доступных ответов, но посмотрю на них. Выбор строится вокруг двух оценочных вопросов:
E1. Где знание принадлежит бизнес-логике?
E2. Где будет выглядеть следующий читатель кода? (Коварный очевидный код)
Некоторые варианты:
-
В клиентском коде (объект, который выполняет "новый SupportTicket" ). По-видимому, это, вероятно, не должно/не должно знать бизнес-логику, иначе вы не захотите создать этот причудливый конструктор. Если это подходящее место для бизнес-логики, тогда оно должно сказать:
$ticket = new SupportTicket($customer, $title);
handleNewSupportTicket($ticket, ...other parameters...)
где, чтобы защитить E2, "handlenewSupportTicket" - это место, где определена эта бизнес-логика (и следующий программист может легко ее найти).
-
В объекте поддержка, как отдельный бизнес-вызов. Лично я не очень доволен этим, потому что это два вызова из кода клиента, где умственная мысль - это одно.
$ticket = new SupportTicket($customer, $title);
$ticket -> handleNewSupportTicket(...other parameters...)
-
В классе билетов поддержки. Здесь ожидается, что бизнес-логика находится в зоне поддержки билетов, но поскольку новые билеты на поддержку абсолютно необходимо обрабатывать немедленно, а не позже, эта важная задача не может быть оставлена никому воображению или туманности, то есть конкретно не к клиентский код. Я знаю только, как кодировать методы класса в Smalltalk, но я сделаю удар в псевдокоде:
$ticket = SupportTicket.createAndHandleNewSupportTicket(...other parameters...)
Предполагая, что код клиента нуждается в дескрипторе нового билета для других целей (иначе "$ ticket =" исчезнет).
Я не очень люблю это, потому что другие программисты не считают естественным искать бизнес-логику в классе или статических методах. Но это третий вариант.
-
Единственный четвертый выбор - если есть еще одно место, где бизнес-логика счастливо проживает, и другие программисты, естественно, будут искать его, и в этом случае он переходит в класс/статическую функцию.
$ticket = SupportTicketBusinessLogic.createAndHandleNewSupportTicket(...other params...)
где эта функция класса/статики выполняет множественные вызовы. (Но теперь мы снова имеем возможность, что билеты можно построить и не обрабатывать должным образом: (.