Melhorando a Arquitetura: Inversão e Injeção de Dependências, Herança e Composição

Olá. Muitas vezes, ao trabalhar com código antigo (e às vezes não muito), ou ao tentar usar algum tipo de biblioteca, você encontra restrições de extensão. Freqüentemente, não haveria problema se o código fosse versado em arquitetura. Existem muitas regras e padrões de arquitetura que, em última análise, tornam mais fácil estender, refatorar e reutilizar o código. Neste artigo, quero abordar alguns deles por meio de exemplos.






Há muito tempo, em um projeto distante, surgiu um serviço que envia um e-mail com uma nova senha aos usuários. Algo assim:





<?php

class ReminderPasswordService
{
    protected function sendToUser($user, $message)
    {
        $this->getMailer()->send([
            'from' => 'admin@example.com',
            'to' => $user['email'],
            'message' => $message
        ]);
    }

    public function sendReminderPassword($user, $password)
    {
        $message = $this->prepareMessage($user, $password);
        $this->sendToUser($user, $message);
    }

    protected function prepareMessage($user, $password)
    {
        $userName = $this->escapeHtml($user['first_name']);
        $password = $this->escapeHtml($password);
        $message = " {$userName}!
           {$password}";

        $message = $this->format($message);
        $message = $this->addHeaderAndFooter($message);

        return $message;
    }

    protected function format($message)
    {
        return nl2br($message);
    }

    protected function escapeHtml($string)
    {
        return htmlentities($string);
    }

    protected function addHeaderAndFooter($message)
    {
        $message = "<html><body>{$message}<br> , !</body>";

        return $message;
    }

    protected function getMailer()
    {
        return new Mailer('user', 'password', 'smtp.example.com');
    }

}
      
      



, .. , , , - . , , , , . - . plainText, HTML. ( , , ).





<?php
class ReminderPasswordCopyToManagerService extends ReminderPasswordService
{
    protected function send($user, $message)
    {
        $this->getMailer()->send([
            'from' => 'admin@example.com',
            'to' => 'manager@example.com',
            'message' => $message
        ]);
    }

    protected function prepareMessage($user, $password)
    {
        $userName = $this->escapeHtml($user['first_name']);
        $message = " {$userName}!
           ****";

        return $message;
    }

    protected function getMailer()
    {
        return new Mailer('user2', 'password2', 'smtp.corp.example.com');
    }
}
      
      



, , . smtp API . Mailer , . , , ?





Dependency Injection ( , DI)

DI - , , - , .





, . , , - . , - , . . Unit . , - DI, . :






<?php
class ReminderPasswordService
{
    /**
     * @var Mailer
     */
    protected $mailer;

    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    //   getMailer,   protected  $mailer

    // ...
}
      
      



, getMailer():





<?php
class ReminderPasswordCopyToManagerService extends ReminderPasswordService
{
    protected function send($to, $message)
    {
        $this->mailer->send([
            'from' => 'admin@example.com',
            'to' => 'manager@example.com',
            'message' => $message
        ]);
    }

    protected function prepareMessage($user, $password)
    {
        $userName = $this->escapeHtml($user['first_name']);
        $message = " {$userName}!
           ****";

        return $message;
    }
}
      
      



, , . , Mailer, ( , , ) . , , .





(Dependency Inversion Principle, DIP)

- , . - .





. , , , . : , .





<?php
interface MailerInterface
{
    public function send($emailFrom, $emailTo, $message);
}
      
      



.. - - MailMessageInterface , .





<?php
interface MailMessageInterface
{
    public function setFrom($from);
    public function getFrom();

    public function setTo($to);
    public function getTo();

    public function setMessage($message);
    public function getMessage();
}
      
      



MailSenderInterface, ,





<?php
interface MailerInterface
{
    public function send(MailMessageInterface $message);
}
      
      



- MailMessageInterface,





<?php
interface MailMessageFactoryInterface
{
    public function create(): MailMessageInterface;
}
      
      



, ,





<?php
class ReminderPasswordService
{
    /**
     * @var MailerInterface
     */
    protected $mailer;

    /**
     * @var MailMessageFactoryInterface
     */
    protected $messageFactory;

    public function __construct(MailerInterface $mailer, MailMessageFactoryInterface $messageFactory)
    {
        $this->mailer = $mailer;
        $this->messageFactory = $messageFactory;
    }

    protected function send($user, $messageText)
    {
        $message = $this->messageFactory->create();
        $message->setFrom('admin@example.com');
        $message->setTo($user['email']);
        $message->setMessage($messageText);

        $this->mailer->send($message);
    }

    //    

    public function sendReminderPassword($user, $password)
    {
        $message = $this->prepareMessage($user, $password);
        $this->sendToUser($user, $message);
    }

    protected function prepareMessage($user, $password)
    {
        $userName = $this->escapeHtml($user['first_name']);
        $password = $this->escapeHtml($password);
        $message = " {$userName}!
           {$password}";

        $message = $this->format($message);
        $message = $this->addHeaderAndFooter($message);

        return $message;
    }

    protected function format($message)
    {
        return nl2br($message);
    }

    protected function escapeHtml($string)
    {
        return htmlentities($string);
    }

    protected function addHeaderAndFooter($message)
    {
        $message = "<html><body>{$message}<br> , !</body>";

        return $message;
    }
}
      
      



, , . .





<?php
class ReminderPasswordCopyToManagerService extends ReminderPasswordService
{
    protected function send($to, $messageText)
    {
        $message = $this->messageFactory->create();
        $message->setFrom('admin@example.com');
        $message->setTo('manager@example.com');
        $message->setMessage($messageText);

        $this->mailer->send($message);
    }

    protected function prepareMessage($user, $password)
    {
        $userName = $this->escapeHtml($user['first_name']);
        $message = " {$userName}!
           ****";

        return $message;
    }
}
      
      



VS

- . - , .





:





1. , .





2. , protected/private





3. , - - .





, , - , , . 90% ( , , ), .





, . , API, -





<?php
class SomeAPIService implements SomeAPIServiceInterface
{
    public function getSomeData($someParam)
    {
        $someData = [];
        // ...
        return $someData;
    }
}
      
      



, , . :





<?php
class SomeApiServiceCached extends SomeAPIService
{
    public function getSomeData($someParam)
    {
        $cachedData = $this->getCachedData($someParam);
        if ($cachedData === null) {
            $cachedData = parent::getSomeData($someParam);
            $this->saveToCache($someParam, $cachedData);
        }

        return $cachedData;
    }

    // ...
}
      
      



API , , DIP, .





<?php

class SomeApiServiceCached implements SomeAPIServiceInterface
{
   private $someApiService;

    public function __construct(SomeApiServiceInterface $someApiService)
    {
        $this->someApiService = $someApiService;
    }

    public function getSomeData($someParam)
    {
        $cachedData = $this->getCachedData($someParam);
        if ($cachedData === null) {
            $cachedData = $this->someApiService->getSomeData($someParam);
            $this->saveToCache($someParam, $cachedData);
        }

        return $cachedData;
    }

    // ...
}
      
      



, , .





ReminderPasswordCopyToManagerService , " ". , - addHeaderAndFooter format, prepareMessage ( - (Open-Closed Principe), , ),





Geral - corpo da mensagem, método escapeHtml .





Vamos tentar colocar o geral em classes separadas.





<?php

class ReminderPasswordMessageTextBuilder
{
    public function buildMessageText($userName, $password)
    {
        return " {$userName}!
           {$password}";
    }
}

class Escaper
{
    public function escapeHtml($string)
    {
        return htmlentities($string);
    }
}
      
      



Se olharmos para as diferenças, então, em geral, ambos os serviços diferem apenas no texto da mensagem, bem como nos destinatários. Vamos reescrever os dois serviços para que sejam independentes um do outro e contenham apenas diferenças.





<?php
class ReminderPasswordService
{
    //  ,    
    private $mailer;
    private $messageFactory;
    private $escaper;
    private $messageTextBuilder;

    public function __construct(
        MailerInterface $mailer,
        MailMessageFactoryInterface $messageFactory,
        Escaper $escaper,
        ReminderPasswordMessageTextBuilder $messageTextBuilder
    ) {
        $this->mailer = $mailer;
        $this->messageFactory = $messageFactory;
        $this->escaper = $escaper;
        $this->messageTextBuilder = $messageTextBuilder;
    }

    public function sendReminderPassword($user, $password)
    {
        $messageText = $this->prepareMessage($user, $password);

        $message = $this->messageFactory->create();
        $message->setFrom('admin@example.com');
        $message->setTo($user['email']);
        $message->setMessage($messageText);

        $this->mailer->send($message);
    }

    private function prepareMessage($user, $password)
    {
        $userName = $this->escaper->escapeHtml($user['first_name']);
        $password = $this->escaper->escapeHtml($password);
        $message = $this->messageTextBuilder->buildMessageText($userName, $password);
        $message = $this->format($message);
        $message = $this->addHeaderAndFooter($message);

        return $message;
    }

    //        .
    private function addHeaderAndFooter($message)
    {
        $message = "<html><body>{$message}<br> , !</body>";

        return $message;
    }

    private function format($message)
    {
        return nl2br($message);
    }
}
      
      



e ex-herdeiro





<?php
class ReminderPasswordCopyToManagerService
{
    private $mailer;
    private $messageFactory;
    private $escaper;
    private $messageTextBuilder;

    public function __construct(
        MailerInterface $mailer,
        MailMessageFactoryInterface $messageFactory,
        Escaper $escaper,
        ReminderPasswordMessageTextBuilder $messageTextBuilder
    ) {
        $this->mailer = $mailer;
        $this->messageFactory = $messageFactory;
        $this->escaper = $escaper;
        $this->messageTextBuilder = $messageTextBuilder;
    }

    public function sendReminderPasswordCopyToManager($user)
    {
        $messageText = $this->prepareMessage($user);

        $message = $this->messageFactory->create();
        $message->setFrom('admin@example.com');
        $message->setTo($user['email']);
        $message->setMessage($messageText);

        $this->mailer->send($message);
    }

    private function prepareMessage($user)
    {
        $userName = $this->escaper->escapeHtml($user['first_name']);
        $message = $this->messageTextBuilder->buildMessageText($userName, '****');

        return $message;
    }
}
      
      



Assim, embora as classes tenham adquirido várias dependências, tornou-se visivelmente mais conveniente cobrir com testes ou reutilizar seções individuais do código. Livramo-nos da conexão entre eles e podemos desenvolver facilmente cada classe separada uma da outra.









PS Claro, essas aulas ainda estão longe do ideal, mas mais disso em outro momento.








All Articles