Является ли бизнес-логика в конструкторах хорошей идеей?

В настоящее время я перестраиваю специализированную систему билетов на работу (в основном используется для поддержки людей с ошибками в оборудовании для дистанционного зондирования...). Во всяком случае, мне было интересно, является ли хорошая работа типа рабочего процесса конструктором объекта.

Например, в настоящее время это:

$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...)
    

    где эта функция класса/статики выполняет множественные вызовы. (Но теперь мы снова имеем возможность, что билеты можно построить и не обрабатывать должным образом: (.