Verbesserung der Architektur: Abhängigkeitsinversion und -injektion, Vererbung und Zusammensetzung

Hallo. Wenn Sie mit altem (und manchmal nicht so) Code arbeiten oder versuchen, eine Bibliothek zu verwenden, stoßen Sie sehr oft auf Erweiterungsbeschränkungen. Oft wäre es kein Problem, wenn der Code architektonisch kompetent wäre. Es gibt viele Architekturregeln und -muster, die es letztendlich einfacher machen, Ihren Code zu erweitern, umzugestalten und wiederzuverwenden. In diesem Artikel möchte ich einige davon in Beispielen ansprechen.






Vor langer Zeit erschien in einem weit entfernten Projekt ein Dienst, der eine E-Mail mit einem neuen Passwort an Benutzer sendet. Etwas wie das:





<?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), , ),





Allgemein - Nachrichtentext, EscapeHtml- Methode .





Versuchen wir, den General in getrennte Klassen zu bringen.





<?php

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

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



Wenn wir uns die Unterschiede ansehen, unterscheiden sich beide Dienste im Allgemeinen nur im Text der Nachricht sowie in den Empfängern. Lassen Sie uns beide Dienste so umschreiben, dass sie unabhängig voneinander sind und nur Unterschiede enthalten.





<?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);
    }
}
      
      



und ehemaliger Erbe





<?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;
    }
}
      
      



Obwohl die Klassen eine Reihe von Abhängigkeiten erworben haben, ist es daher viel bequemer geworden, einzelne Codeabschnitte mit Tests zu behandeln oder wiederzuverwenden. Wir haben die Verbindung zwischen ihnen beseitigt und können leicht jede einzelne Klasse unabhängig von der anderen entwickeln.









PS Natürlich sind diese Klassen noch lange nicht ideal, aber dazu ein anderes Mal mehr.








All Articles