В приложении с объектно-ориентированной архитектурой часто встает вопрос: кто должен отвечать за создание того или иного объекта? Многие современные решения используют Локатор служб для инстанциации и хранения объектов инфраструктуры и даже некоторых элементов бизнес-модели, но этого часто не достаточно. Рассмотрим простой пример:
Предполагается, что использоваться эти классы будут в методах Order::pay
(оплата заказа), Auth::registerNewUser
(регистрация нового пользователя) и Article::create
(создание новой статьи).
Какая часть системы должна отвечать за создание экземпляров класса MailMessage
и следует ли вызывать метод send
сразу после инстанциации?
Фабричный метод
Паттерн Фабричный метод достаточно прост в реализации и использовании. Он имеет следующую структуру:
Его задача заключается в том, чтобы организовать специальный метод, который будет отвечать за создание объектов данного или другого класса и возвращать их.
Чем же фабричный метод лучше обычного конструктора? Тем что он позволяет сформировать конфигурацию (параметры) конструктора перед его вызовом. Так, для создания соединения с БД необходимо прежде получить конфигурацию для этого соединения, и фабричный метод позволит собрать всю логику инстанциации в одном месте:
Как видно из примера, фабричный метод может быть статичным. Часто это используется для инстанциации вызываемого класса. Не статичные методы больше применяются для создания экземпляров других классов.
Вернемся к нашей задаче из вводной. Где разместить фабричный метод, который будет создавать экземпляры класса MailMessage
? Учитывая что для рассылки писем необходим адресат (свойство $to
), логичным решением будет разместить его в классе User
с именем notifyFromEmail
. Метод будет создавать сообщение для вызываемого пользователя:
Теперь имея ссылку на пользователя мы можем легко оповестить его с помощью письма об интересных событиях:
Работа с объектом в фабричном методе
Из предыдущих примеров у вас может возникнуть желание вызывать метод send
прямо в фабричном методе notifyFromEmail
. Не делайте так! Запомните - фабричный метод должен только создавать и конфигурировать объекты, а не взаимодействовать с ними. За сохранение созданного объекта в базе данных или передачу созданного сообщения по электронной почте должен отвечать объект, вызвавший фабричный метод или инфраструктура системы. Запомнили? Хорошо. Теперь попробуем понять почему так.
Дело в том, что взаимодействие с объектом внутри фабричного метода делает решение менее гибким. Рассмотрим пример:
После вызова registerNewUser
предполагается, что регистрируемый пользователь будет оповещен с помощью email-рассылки и появляется желание добавить эту логику прямо в метод. Но что делать, если мы не должны рассылать уведомления пользователям, зарегистрированным в системе посредством выгрузки из CSV файла? В этом случае нам придется повторить логику фабричного метода registerNewUser
без оповещения. В данном случае проще будет отказаться от взаимодействия с создаваемым внутри метода объектом:
Как видите, сохранение зарегистрированного пользователя в базе данных так же выполняется не внутри фабричного метода registerNewUser
, а в соответствующих методах контроллера.