Пример применения паттерна "Фабричный метод". В чём преимущество?

Здесь приведён пример из реальной жизни: https://refactoring.guru/ru/design-patterns/factory-method/php/example#example-1

У меня два вопроса касательно этого примера:

  1. В чём преимущетсва данных классов-фабрик? Абстрагировать клиентский код от конкретных классов? Но ведь можно это сделать и без фабрик вовсе. Я переписал данный пример без фабрик и привязки к конкретному классу-коннектору: https://pastebin.com/c6bEHbYw

  2. Почему в классе SocialNetworkPoster присутствует метод post, который не относится к его обязанности? Не нарушает ли это SRP?


Ответы (1 шт):

Автор решения: Ivan Dudarev

1 Вопрос

UPD. Это шаблон Фабрика

Не всегда мы знаем, экземпляр какого именно класса нужно создать. Например, я захотел написать HTTP клиент по PSR:

namespace Psr\Http\Client;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

interface ClientInterface
{
    /**
     * Sends a PSR-7 request and returns a PSR-7 response.
     *
     * @param RequestInterface $request
     *
     * @return ResponseInterface
     *
     * @throws \Psr\Http\Client\ClientExceptionInterface If an error happens while processing the request.
     */
    public function sendRequest(RequestInterface $request): ResponseInterface;
}

При этом я не хочу обременять пользователей какой-то конкретной реализацией PSR-7

Тогда я в конструкторе своего клиента буду получать фабрику ответа (фабрики HTTP-сообщений, кстати, тоже есть в PSR, это PSR-17).

namespace Some\Namespace;


use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;

class HttpClient implements ClientInterface
{

    /**
     * @var ResponseFactoryInterface
     */
    private $responseFactory;

    public function __construct(ResponseFactoryInterface $responseFactory)
    {
        $this->responseFactory = $responseFactory;
    }    

    public function sendRequest(RequestInterface $request): ResponseInterface
    {
        // ...
        // посылаю запрос, получаю сырой ответ
        // ...
        $response = $this->responseFactory
            ->createResponse($status, $phrase)
            ->withHeader('Content-Type', $contentType)
        ;
        $response->getBody()->write($contents);
        $response->getBody()->rewind();
        return $response;
    }
}

Теперь пользователь моей библиотеки точно знает, какого класса будет ответ.

Шаблон фабричный метод

Иногда в конструкторе класса можно передать параметры, значения которых зависят друг от друга и программист может допустить ошибку.


class Address
{
    public function __construct($street, $build, $letter, $apartment)
    {
    }
}

По адресу может быть частный дом и там квартира не нужна.

Можно запретить явно вызывать конструктор и сделать фабричные методы


class Address
{
    private function __construct($street, $build, $letter, $apartment)
    {
    }

    public static function createApartment($street, $build, $letter, $apartment)
    {
        return new self($street, $build, $letter, $apartment);
    }

    public static function createHome($street, $build, $letter)
    {
        return new self($street, $build, $letter, null);
    }
}

Теперь программист не ошибется.

2 Вопрос.

Возможно, данный пример и нарушает SRP, но в целом пример остается корректным, т.к. не имеет отношения к принципу единой ответственности.

→ Ссылка