PHP: Абстрактная фабрика

Abstract Factory Абстрактная фабрика Abstract Factory

Абстрактная фабрика — это порождающий паттерн проектирования, который решает проблему создания целых семейств связанных продуктов, без указания конкретных классов продуктов.

Абстрактная фабрика задаёт интерфейс создания всех доступных типов продуктов, а каждая конкретная реализация фабрики порождает продукты одной из вариаций. Клиентский код вызывает методы фабрики для получения продуктов, вместо самостоятельного создания с помощью оператора new. При этом фабрика сама следит за тем, чтобы создать продукт нужной вариации.

Подробней об Абстрактной фабрике

Особенности паттерна в PHP

Сложность:

Популярность:

Применимость: Паттерн можно часто встретить в PHP-коде, особенно там, где требуется создание семейств продуктов (например, во всевозможных фреймворках).

Пример: Структура паттерна

AbstractFactoryStructure.php: Пример

<?php

namespace RefactoringGuru\AbstractFactory\Structure;

/**
 * Abstract Factory Design Pattern
 *
 * Intent: Provide an interface for creating families of related or dependent
 * objects without specifying their concrete classes.
 */

/**
 * The Abstract Factory interface declares a set of methods that return
 * different abstract products. These products are related and called a family.
 * Products of one family are usually able to collaborate among themselves. A
 * family of products may have several variations, but the products of one
 * variation are incompatible with products of another.
 */
interface AbstractFactory
{
    public function createProductA(): AbstractProductA;

    public function createProductB(): AbstractProductB;
}

/**
 * Concrete Factories produce a family of products that belong to a single
 * variation. The factory guarantees that resulting products are compatible.
 * Note that signatures of the Concrete Factory's methods return an abstract
 * product, while inside the method a concrete product is instantiated.
 */
class ConcreteFactory1 implements AbstractFactory
{
    public function createProductA(): AbstractProductA
    {
        return new ConcreteProductA1();
    }

    public function createProductB(): AbstractProductB
    {
        return new ConcreteProductB1();
    }
}

/**
 * Each concrete factory has a corresponding product variation.
 */
class ConcreteFactory2 implements AbstractFactory
{
    public function createProductA(): AbstractProductA
    {
        return new ConcreteProductA2();
    }

    public function createProductB(): AbstractProductB
    {
        return new ConcreteProductB2();
    }
}

/**
 * Each distinct product of a product family should have a base interface. All
 * variations of the product must implement this interface.
 */
interface AbstractProductA
{
    public function usefulFunctionA();
}

/**
 * Concrete Products are created by corresponding Concrete Factories.
 */
class ConcreteProductA1 implements AbstractProductA
{
    public function usefulFunctionA()
    {
        return "The result of the product A1.";
    }
}

class ConcreteProductA2 implements AbstractProductA
{
    public function usefulFunctionA()
    {
        return "The result of the product A2.";
    }
}

/**
 * The base interface of another product. All products can interact with each
 * other, but proper interaction is possible only between products of the same
 * concrete variation.
 */
interface AbstractProductB
{
    /**
     * The ProductB is able to do its own thing...
     */
    public function usefulFunctionB();

    /**
     * ...but it also can collaborate with the ProductA.
     *
     * The Abstract Factory makes sure that all products it creates are of the
     * same variation and thus, compatible.
     */
    public function anotherUsefulFunctionB(AbstractProductA $collaborator);
}

/**
 * Concrete Products are created by corresponding Concrete Factories.
 */
class ConcreteProductB1 implements AbstractProductB
{
    public function usefulFunctionB()
    {
        return "The result of the product B1.\n";
    }

    /**
     * The product B1 is only able to work correctly with the product A1.
     * Nevertheless, it accepts any instance of Abstract Product A as an
     * argument.
     */
    public function anotherUsefulFunctionB(AbstractProductA $collaborator)
    {
        $result = $collaborator->usefulFunctionA();

        return "The result of the B1 collaborating with the ({$result})";
    }
}

class ConcreteProductB2 implements AbstractProductB
{
    public function usefulFunctionB()
    {
        return "The result of the product B2.\n";
    }

    /**
     * The product B2 is only able to work correctly with the product A2.
     * Nevertheless, it accepts any instance of Abstract Product A as an
     * argument.
     */
    public function anotherUsefulFunctionB(AbstractProductA $collaborator)
    {
        $result = $collaborator->usefulFunctionA();

        return "The result of the B2 collaborating with the ({$result})";
    }
}

/**
 * The client code works with factories and products only through abstract
 * types: AbstractFactory and AbstractProduct. It lets you pass any factory or
 * product subclass to the client code without breaking it.
 */
function clientCode(AbstractFactory $factory)
{
    $product_a = $factory->createProductA();
    $product_b = $factory->createProductB();

    print($product_b->usefulFunctionB());
    print($product_b->anotherUsefulFunctionB($product_a));
}

/**
 * The client code can work with any concrete factory class.
 */
print("Client: Testing client code with the first factory type:\n");
clientCode(new ConcreteFactory1());
print("\n\n");

print("Client: Testing the same client code with the second factory type:\n");
clientCode(new ConcreteFactory2());

Output.txt: Результат выполнения

Client: Testing client code with the first factory type:
The result of the product B1.
The result of the B1 collaborating with the (The result of the product A1.)

Client: Testing the same client code with the second factory type:
The result of the product B2.
The result of the B2 collaborating with the (The result of the product A2.)

Пример: Пример из жизни

AbstractFactoryRealLife.php: Пример

<?php

namespace RefactoringGuru\AbstractFactory\RealLife;

/**
 * Паттерн Абстрактная Фабрика
 *
 * Назначение: Предоставляет интерфейс для создания семейств связанных или
 * зависимых объектов, без привязки к их конкретным классам.
 *
 * Пример: В этом примере паттерн Абстрактная Фабрика предоставляет
 * инфраструктуру для создания нескольких разновидностей шаблонов для одних и
 * тех же элементов веб-страницы.
 *
 * Чтобы веб-приложение могло поддерживать сразу несколько разных движков
 * рендеринга страниц, его классы должны работать с шаблонами только через
 * интерфейсы, не привязываясь к их конкретным классам. В то же время, объекты
 * приложения не должны создавать шаблоны напрямую, а поручать это спецальным
 * объектам фабрик, с которыми тоже надо работать через абстрактный интерфейс.
 *
 * Благодаря этому, вы можете подать в приложение фабрику, соотвествующую одному
 * из движков рендеринга, зная что с этого момента, все шаблоны будут
 * порождаться именно этой фабрикой, и будут соотвествовать движку рендеринга
 * этой фабрики. Если вы захотите сменить движок рендеринга, то всё что нужно
 * будет сделать — это подать в приложение объект фабрики другого типа и ничего
 * при этом не сломается.
 */

/**
 * Abstract Factory.
 */
interface TemplateFactory
{
    public function createTitleTemplate(): TitleTemplate;

    public function createPageTemplate(): PageTemplate;
}

/**
 * Concrete Factory. Creates Twig variant of products.
 */
class TwigTemplateFactory implements TemplateFactory
{
    public function createTitleTemplate(): TitleTemplate
    {
        return new TwigTitleTemplate();
    }

    public function createPageTemplate(): PageTemplate
    {
        return new TwigPageTemplate();
    }
}

/**
 * Concrete Factory. Creates PHPTemplate variant of products.
 */
class PHPTemplateFactory implements TemplateFactory
{
    public function createTitleTemplate(): TitleTemplate
    {
        return new PHPTitleTemplate();
    }

    public function createPageTemplate(): PageTemplate
    {
        return new PHPPageTemplate();
    }
}

/**
 * Abstract product: the page title template.
 */
interface TitleTemplate
{
    public function render(): string;
}

/**
 * Concrete product. Twig variant.
 */
class TwigTitleTemplate implements TitleTemplate
{
    public function render(): string
    {
        return "<h1>{{ title }}</h1>";
    }
}

/**
 * Concrete product. PHPTemplate variant.
 */
class PHPTitleTemplate implements TitleTemplate
{
    public function render(): string
    {
        return "<h1><?php print(\$title) ?></h1>";
    }
}

/**
 * Abstract product: the whole page template.
 */
interface PageTemplate
{
    public function render(TitleTemplate $titleTemplate): string;
}

/**
 * Concrete product. Twig variant.
 */
class TwigPageTemplate implements PageTemplate
{
    public function render(TitleTemplate $titleTemplate): string
    {
        $title = $titleTemplate->render();
        return <<<EOF
<div class="page">
  $title
  <article class="content">{{ content }}</article>
</div>
EOF;
    }
}

/**
 * Concrete product. PHPTemplate variant.
 */
class PHPPageTemplate implements PageTemplate
{
    public function render(TitleTemplate $titleTemplate): string
    {
        $title = $titleTemplate->render();
        return <<<EOF
<div class="page">
  $title
  <article class="content"><?php print(\$content) ?></article>
</div>
EOF;
    }
}

/**
 * Client code.
 */
function templateRenderer(TemplateFactory $factory)
{
    $titleTemplate = $factory->createTitleTemplate();
    $pageTemplate = $factory->createPageTemplate();

    print($pageTemplate->render($titleTemplate));
}

/**
 * The client code can be accept a factory object of any type.
 */
print("Testing rendering with the Twig factory:\n");
templateRenderer(new TwigTemplateFactory());
print("\n\n");

print("Testing rendering with the PHPTemplate factory:\n");
templateRenderer(new PHPTemplateFactory());

Output.txt: Результат выполнения

Testing rendering with the Twig factory:
<div class="page">
  <h1>{{ title }}</h1>
  <article class="content">{{ content }}</article>
</div>

Testing rendering with the PHPTemplate factory:
<div class="page">
  <h1><?php print($title) ?></h1>
  <article class="content"><?php print($content) ?></article>
</div>