Является ли бизнес-логика в конструкторах хорошей идеей? - PullRequest
17 голосов
/ 07 марта 2009

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

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

$ticket = new SupportTicket(
    $customer,
    $title,
    $start_ticket_now,
    $mail_customer
);

как только объект будет создан, он поместит строку в базу данных, отправит клиенту электронное письмо с подтверждением, возможно отправит текстовое сообщение ближайшему техническому специалисту и т. Д.

Должен ли конструктор запускать всю эту работу или что-то в этом роде?

$ticket = new SupportTicket($customer, $title);
$customer->confirmTicketMailed($ticket);
$helpdesk->alertNewTicket($ticket);

Если это помогает, все объекты основаны на стиле ActiveRecord.

Полагаю, это может быть вопросом мнения, но как вы думаете, что лучше всего делать?

Ответы [ 5 ]

44 голосов
/ 07 марта 2009

Если конструктор выполняет всю эту работу, он знает о многих других объектах домена. Это создает проблему зависимости. Должен ли ticket действительно знать о Customer и HelpDesk? Когда добавляются новые функции, разве маловероятно, что новые объекты домена будут добавлены в рабочий процесс, и не означает ли это, что нашим бедным ticket придется знать о постоянно растущей совокупности объектов домена?

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

Еще одна причина, по которой мне не нравятся конструкторы, которые делают что-то, заключается в том, что это делает объекты вокруг них очень сложными для тестирования. Мне нравится писать много фиктивных объектов. Когда я пишу тест против customer, я хочу пройти его по макету ticket. Если конструктор ticket контролирует рабочий процесс и танец между customer и другими объектами домена, то маловероятно, что я смогу смоделировать его, чтобы протестировать customer.

Я предлагаю вам прочитать Принципы SOLID , статью, которую я написал несколько лет назад об управлении зависимостями в объектно-ориентированных проектах.

4 голосов
/ 07 марта 2009

Проблема, с точки зрения проектирования ОО, заключается не столько в том, должна ли эта функциональность быть реализована в конструкторе (в отличие от других методов этого класса), а в том, должен ли класс SupportTicket знать, как делать все эти вещи.

В двух словах, класс SupportTicket должен моделировать билет поддержки, а не только билет поддержки. Создание электронного письма, знание того, как отправить это электронное письмо клиенту, постановка в очередь заявки на обработку и т. Д., - это все функциональные возможности, которые вы должны удалить из своего класса SupportTicket и инкапсулировать в другом месте. Преимущества этого включают более низкое сцепление, более высокую когезию и улучшенную тестируемость.

Взгляните на Принцип единой ответственности , который объясняет преимущества этого. В частности, этот блог является хорошим местом для чтения SRP и других ключевых принципов хорошего ОО-дизайна.

4 голосов
/ 07 марта 2009

Разделите вещи. На самом деле, вы не хотите, чтобы билет знал, как он должен отправлять электронную почту и т. Д. Это работа такой службы, как класс.

[Обновление] Я не думаю, что предложенные фабричные образцы хороши для этого. Фабрика полезна, если вы хотите создавать разные реализации заявок, не помещая эту логику в сам билет (например, через перегруженные конструкторы).

Взгляните на концепцию сервиса, предложенную в доменно-управляемом дизайне.

Услуги : когда операция концептуально не принадлежит ни одному объекту. Следуя естественным контурам проблемы, вы можете реализовать эти операции в сервисах. Концепция сервиса называется «Чистое изготовление» в GRASP.

2 голосов
/ 08 марта 2009

Короткий ответ - нет.

При проектировании аппаратного обеспечения у нас была поговорка: «Не ставьте затвор на часах или линии сброса - это заслоняет логику». То же самое относится и здесь по той же причине.

Более длинный ответ придется подождать, но см. " Screechingly Очевидный код ".

1 голос
/ 09 марта 2009

На самом деле я никогда не был доволен ни одним из доступных ответов, но давайте посмотрим на них. Выбор основывается на двух вопросах оценки:

E1. Где знание принадлежит бизнес-логике?

E2. Где будет искать следующий читатель кода? ( Страшно очевидный код )

Некоторые варианты:

  • В клиентском коде (объект, который выполняет "новый SupportTicket"). Очевидно, он не знает / не должен знать бизнес-логику, иначе вы бы не захотели создать этот модный конструктор. Если это правильное место для бизнес-логики, тогда оно должно сказать:

    $ticket = new SupportTicket($customer, $title);
    
    handleNewSupportTicket($ticket, ...other parameters...)
    

    где для защиты E2 «handlenewSupportTicket» - это место, где определена эта бизнес-логика (и следующий программист может легко ее найти).

  • В объекте поддержки , как отдельный бизнес-вызов. Лично я не очень доволен этим, потому что это два вызова из клиентского кода, где мысленная мысль - это 1 вещь.

    $ticket = new SupportTicket($customer, $title);
    
    $ticket -> handleNewSupportTicket(...other parameters...)
    
  • В классе поддержки . Здесь ожидается, что бизнес-логика находится в области билетов поддержки, но поскольку новые заявки поддержки должны быть обработаны немедленно, а не позже, эта важная задача не может быть оставлена ​​на усмотрение или опечатку, т. Е. Конкретно не код клиента. Я знаю только, как кодировать методы класса в Smalltalk , но я попробую псевдокод:

    $ticket = SupportTicket.createAndHandleNewSupportTicket(...other parameters...)
    

    Предполагая, что клиентскому коду нужен дескриптор нового тикета для других целей (в противном случае "$ ticket =" исчезнет).

    Мне это не очень нравится, потому что другие программисты не находят настолько естественным поиск бизнес-логики в классе или статических методах. Но это третий вариант.

  • Единственный четвертый выбор - если есть другое место, в котором счастливо проживает бизнес-логика, и другие программисты будут естественно искать ее, и в этом случае она переходит в классовую / статическую функцию там.

    $ticket = SupportTicketBusinessLogic.createAndHandleNewSupportTicket(...other params...)
    

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...