Шаблон проектирования
Пытаюсь следовать шаблонам проектирования и встрял на полпути. Задача такая, есть 3 способа отправки сообщения (почта, смс, телеграмм), в почте есть еще 3 варианта (тк в программе три разных модуля для работы, то и типы входящих данных для писем отличаются, как и сам шаблон письма).
Мне удалось с помощью фабричного метода сделать первую часть вот так:
MessengerInterface
<?php
declare(strict_types = 1);
namespace App\Http\Controllers\Email\Interfaces;
interface MessengerInterface
{
public function send();
}
MailMessenger
<?php
declare(strict_types = 1);
namespace App\Http\Controllers\Email;
use App\Http\Controllers\Email\Interfaces\MessengerInterface;
class MailMessenger implements MessengerInterface
{
public function __construct(
String $fromAddress,
String $toAddress,
Boolean $inbox,
String $messageText,
String $messageHtml,
)
{
}
public function send()
{
return '';
}
}
MessengerFactory
<?php
declare(strict_types = 1);
namespace App\Http\Controllers\Email\Interfaces;
interface MessengerFactory
{
public function createMessenger() : MessengerInterface;
}
MessengerFactory
<?php
declare(strict_types = 1);
namespace App\Http\Controllers\Email;
use App\Http\Controllers\Email\MailMessenger;
use App\Http\Controllers\Email\Interfaces\MessengerFactory;
class MailMessengerFactory implements MessengerFactory
{
public function createMessenger() : MessengerInterface
{
return new MailMessenger;
}
}
Но как впилить разделение по шаблонам внутри самой почты - хз. Точнее хз как это сделать используя шаблоны проектирования. Учитывая, что я до сих пор не понимаю зачем для такой простой вещи, используя шаблон, мне потребовалось аж 2 класса, хотя как по мне и 1 достаточно
Ответы (2 шт):
Вы все верно начали, только сообщение и получатель (юзер в вашей системе) должен быть вынесен в метод, так как это общие данные, да и писем можно за раз несколько разослать:
<?php
declare(strict_types = 1);
namespace App\Http\Controllers\Email\Interfaces;
interface MessengerInterface
{
public function send($to, $message);
}
Далее создаете множество реализаций:
class MailMessenger implements MessengerInterface
{
public function __construct(
MessageViewInterface $view / тут разные вьюхи для разных шаблонов писем,
MailerIntarface $mailer
)
public function send($user, $message)
{
$this->mailer->send($user->getEmail(), $this->view->render($message));
}
}
class ViberMessenger implements MessengerInterface
{
public function __construct(
ViberClient $viberClient;
)
public function send($user, $message)
{
$this->viberClient->send($user->getViberPhone(), $message);
}
}
А потом просто в своем DI (через сервис провайдер в ларавеле или через service.yml в симфони и т.д.) подставляете реализацию, и ваш код во всей системе зависит только от send() который с одинаковым интерфейсом:
$container['MessengerInterface'] = new MailMessenger(...);
или
$container['MessengerInterface'] = new ViberMessenger(...);
Чтобы в DI не сувать вручную классы, можете создать фабрику, будет что-то примерно такое:
$container['MessengerInterface'] = MailMessengerFactory->getMessenger();
$container['MessengerInterface'] = ViberMessengerFactory->getMessenger();
Или например вам нужно сделать так, чтобы рассылка была несколькими сразу мейлерами:
Class MessageSender(){
$messengers=[];
public function addMessenger(MessengerInterface $messenger){
$this->messangers[]=$messenger;
}
public function send(User $user, string $message){
foreach( $this->messengers as $messenger)$messenger->send($user,$message);
}
}
$sender = new MailSender();
$sender->addMessenger(new MailMessenger(...));
$sender->addMessenger(new ViberMessenger(...));
$sende->send($user,$message);
Дополнение с фабриками (это я беру архитектуру более менее средне-сложных систем, для какого либо бложика - оверхэд). Реализацию можно менять, тут главное смысл:
// Интерфейс фабрики шаблонов для писем.
Interface ViewMailFactoryInterface{
public function getConfirmPasswordView():ConfirmPasswordViewInterface;
public function getOrderInfoView():OrderPasswordViewInterface;
}
// Интерфейс шаблона подтверждения письма
Interface ConfirmPasswordViewInterface{
public function setUser(User $user);
public function setConfirmLink(string $link);
}
// Интерфейс шаблона информации о заказе
Interface ConfirmOrderViewInterface{
public function setUser(User $user);
public function setOrder(Order $order);
}
// Фабрика создает зимние шаблоны
class WinterMailFactory{
public function getConfirmPasswordView()
{
return new WinterConfirmPasswordView();
}
public function getOrderInfoView(){
return new WinterOrderPasswordView();
}
}
// Фабрика создает летние шаблоны
class SummerMailFactory{
public function getConfirmPasswordView()
{
return new SummerConfirmPasswordView();
}
public function getOrderInfoView(){
return new SummerOrderPasswordView();
}
}
Дубликат первого сообщения, с дополнениями:
interface MessengerInterface
{
public function sendConfirmPasswordMessage(User $user, $link);
public function sendOrderInfoMessage(Order $order, User $user);
}
Далее создаете множество реализаций:
class MailMessenger implements MessengerInterface
{
public function __construct(
ViewMailFactoryInterface $viewFactory
MailerIntarface $mailer
)
public function sendConfirmPasswordMessage(User $user, $link){
$view = $viewFactory->getConfirmPasswordView();
$view->setUser($user)->setLink($link);
$this->mailer->send($user->getEmail(), $view->render());
}
public function sendOrderInfoMessage(Order $order, User $user){
$view = $viewFactory->getOrderInfoView();
$view->setUser($user)->setOrder($order);
$this->mailer->send($user->getEmail(), $view->render());
}
}
class ViberMessenger implements MessengerInterface
{
public function __construct(
ViberClient $viberClient;
)
public function sendConfirmPasswordMessage(User $user, $link){
$this->viberClient->send($user->getPhone(), $message='Подтвердите пароль, ссылка: '.$link);
}
public function sendOrderInfoMessage(Order $order, User $user){
$this->viberClient->send($user->getPhone(), $message='Номер вашего заказа'.$order->getId());
}
}
Контроллеры:
public function createOrderAction(Reqest $request,MessengerInterface $messenger){
.....
$messenger->sendInfoOrderMessage($order, $user);
}
public function confirmPasswordAction(Reqest $request,MessengerInterface $messenger, ConfirmLinkGenerator $linkGen){
.....
$link = $linkGen->getConfirmLinkForUser($user);
$messenger->sendConfirmPasswordMessage($user, $link);
}
Что самое главное - вашим контроллерам совершенно не надо знать - какой мессенджер используется. А вы в свою очередь настраиваете в сервис провайдерах (у вас вроде Ларавел) - сначала какой месседжер (мейл или вайбер), а если мейл - то для него выбираете нужную фабрику шаблонов писем (зимнюю, летнюю и т.д.). Можно и для вайбера какие либо шаблоны создать, если это надо. Все зависит от требований.
Если абстрагироваться еще дальше. Почему мы ссылаемся именно на пользователя (кому мы посылаем письмо), на его класс? Это нарушает D у SOLID - мы должны зависеть только от интерфейсов. А кому мы еще можем отсылать письма? Допустим у нас появилась сущность Огранизации/оптовики - и все, мы не можем отсылать им письма. Как это решить? Вводим абстракцию MessageTargetInterface например
Interface MessageTargetInterface{
public function getPhone();
public function getEmail();
}
class User implements MessageTargetInterface{
}
class Organization Implements MessageTargetInterface{
}
//меняем у интерфейса менеджеров - кому отправляем. С пользователя - на интерфейс.
interface MessengerInterface
{
public function sendConfirmPasswordMessage(MessageTargetInterface $messageTarget, $link);
public function sendOrderInfoMessage(MessageTargetInterface messageTarget, Order $order);
}
И все, теперь : 1) Мы можем слать письма и сообщения любой сущности - хоть пользователю, хоть организации, хоть скайнету. Главное чтобы они имели интерфейс, который гарантирует наличие у них телефона и емейла. + мы можем вводить сколько угодно таких сущностей не меняя шаблоны и методы отправки. 2) Любой из этих сущностей мы можем слать письма, на емейл, телефон, дозвон роботом. Что только придумаем.