Abstract Factory (Design Patterns)

  • Khái niệm: Abstract Factory là một pattern dành cho thiết kế hướng đối tượng trong phần mềm, nó cung cấp một một lớp giao diện có chức năng tạo ra các đối tượng liên quan mà không chỉ ra những lớp cụ thể nào ở thời điểm thiết kế (Nó thuộc loại Creational pattern).
  • Vấn đề:

+ Chúng ta đang sở hữu một cửa hàng đồ gỗ nội thất và chúng ta đang có các sản phẩm như sau: Chair + Sofa + CoffeeTable. Với những sản phẩm đang bán này,chúng ta có thêm 3 phong cách của sản phẩm là: Modern, Victorian, ArtDeco.

+ Vấn đề chúng ta đang gặp phải là: làm cách nào để tạo ra các sản phẩm cùng 1 phong cách để tạo sự đồng nhất trong 1 căn phòng. Và hiện tại, khách hàng của chúng ta đang không vui vì sản phẩm họ nhận được là Chair phong cách Victorian quý tộc nhưng ghế Sofa lại là phong cách Modern hiện đại.

  • Giải pháp chúng ta cần đó chính là Abstract Factory Pattern:

+ Khai báo các Interface cho từng phong cách sản phẩm.

+ Dựa vào đó ta tiếp tục khai báo Interface sản phẩm cho từng phong cách để tạo ra sự đồng nhất như sau:

  • Cấu trúc:

+ Abstract Products: khai bao các Interface tập hợp những sản phẩm có cùng phong cách.

+ Concrete Products: triển khai các sản phẩm theo từng nhóm phong cách nhất định. Ví dụ: mỗi Chair/Sofa sẽ phải đi theo 1 phong cách là Victorian/Modern.

+ Abstract Factory Interface: đây là Interface tập hợp các method để tạo ra các sản phẩm Abstract.

+ Concrete Factories: thực hiện các method của Abstract Factory. Mỗi Factory sẽ chỉ tạo ra một biến thể sản phẩm tương ứng.Cuối cùng, chúng ta có Client, họ sẽ có thể làm việc với bất cứ một Abstract Factory hay một sản phẩm Abstract nào, miễn là họ làm việc thông qua các Interface.

  • Ưu điểm:

+ Ngay trong ví dụ cụ thể bên trên, chúng ta có thể thấy mỗi Factory sẽ có chính xác class Object được tạo ra theo phong cách nào. Điều này sẽ khiến cho Client dễ dàng xác nhận và làm việc qua các Abstract Interface mà không bị lẫn lộn.

+ Dễ dàng quản lý Concrete Products hơn. Nếu như bạn muốn thay đổi toàn bộ phong cách Object bên trong, bạn chỉ cần thay đổi cấu trúc của Concrete. Ví dụ từ Modern, bạn có thể thay đổi sang phong cách ArtDeco mà không cần thay đổi từng sản phẩm bên trong.

  • Nhược điểm:

+ Code có thể trở nên phức tạp hơn bình thường, vì rất nhiều interfaces và classes mới được giới thiệu cùng với mẫu.

  • Ví dụ thực tế:
<?php
namespace RefactoringGuru\AbstractFactory\RealWorld;

/**
 * The Abstract Factory interface declares creation methods for each distinct
 * product type.
 */
interface TemplateFactory
{
    public function createTitleTemplate(): TitleTemplate;

    public function createPageTemplate(): PageTemplate;

    public function getRenderer(): TemplateRenderer;
}

/**
 * Each Concrete Factory corresponds to a specific variant (or family) of
 * products.
 *
 * This Concrete Factory creates Twig templates.
 */
class TwigTemplateFactory implements TemplateFactory
{
    public function createTitleTemplate(): TitleTemplate
    {
        return new TwigTitleTemplate();
    }

    public function createPageTemplate(): PageTemplate
    {
        return new TwigPageTemplate($this->createTitleTemplate());
    }

    public function getRenderer(): TemplateRenderer
    {
        return new TwigRenderer();
    }
}

/**
 * And this Concrete Factory creates PHPTemplate templates.
 */
class PHPTemplateFactory implements TemplateFactory
{
    public function createTitleTemplate(): TitleTemplate
    {
        return new PHPTemplateTitleTemplate();
    }

    public function createPageTemplate(): PageTemplate
    {
        return new PHPTemplatePageTemplate($this->createTitleTemplate());
    }

    public function getRenderer(): TemplateRenderer
    {
        return new PHPTemplateRenderer();
    }
}

/**
 * Each distinct product type should have a separate interface. All variants of
 * the product must follow the same interface.
 *
 * For instance, this Abstract Product interface describes the behavior of page
 * title templates.
 */
interface TitleTemplate
{
    public function getTemplateString(): string;
}

/**
 * This Concrete Product provides Twig page title templates.
 */
class TwigTitleTemplate implements TitleTemplate
{
    public function getTemplateString(): string
    {
        return "<h1>{{ title }}</h1>";
    }
}

/**
 * And this Concrete Product provides PHPTemplate page title templates.
 */
class PHPTemplateTitleTemplate implements TitleTemplate
{
    public function getTemplateString(): string
    {
        return "<h1><?= \$title; ?></h1>";
    }
}

/**
 * This is another Abstract Product type, which describes whole page templates.
 */
interface PageTemplate
{
    public function getTemplateString(): string;
}

/**
 * The page template uses the title sub-template, so we have to provide the way
 * to set it in the sub-template object. The abstract factory will link the page
 * template with a title template of the same variant.
 */
abstract class BasePageTemplate implements PageTemplate
{
    protected $titleTemplate;

    public function __construct(TitleTemplate $titleTemplate)
    {
        $this->titleTemplate = $titleTemplate;
    }
}

/**
 * The Twig variant of the whole page templates.
 */
class TwigPageTemplate extends BasePageTemplate
{
    public function getTemplateString(): string
    {
        $renderedTitle = $this->titleTemplate->getTemplateString();

        return <<<HTML
        <div class="page">
            $renderedTitle
            <article class="content">{{ content }}</article>
        </div>
        HTML;
    }
}

/**
 * The PHPTemplate variant of the whole page templates.
 */
class PHPTemplatePageTemplate extends BasePageTemplate
{
    public function getTemplateString(): string
    {
        $renderedTitle = $this->titleTemplate->getTemplateString();

        return <<<HTML
        <div class="page">
            $renderedTitle
            <article class="content"><?= \$content; ?></article>
        </div>
        HTML;
    }
}

/**
 * The renderer is responsible for converting a template string into the actual
 * HTML code. Each renderer behaves differently and expects its own type of
 * template strings passed to it. Baking templates with the factory let you pass
 * proper types of templates to proper renders.
 */
interface TemplateRenderer
{
    public function render(string $templateString, array $arguments = []): string;
}

/**
 * The renderer for Twig templates.
 */
class TwigRenderer implements TemplateRenderer
{
    public function render(string $templateString, array $arguments = []): string
    {
        return \Twig::render($templateString, $arguments);
    }
}

/**
 * The renderer for PHPTemplate templates. Note that this implementation is very
 * basic, if not crude. Using the `eval` function has many security
 * implications, so use it with caution in real projects.
 */
class PHPTemplateRenderer implements TemplateRenderer
{
    public function render(string $templateString, array $arguments = []): string
    {
        extract($arguments);

        ob_start();
        eval(' ?>' . $templateString . '<?php ');
        $result = ob_get_contents();
        ob_end_clean();

        return $result;
    }
}

/**
 * The client code. Note that it accepts the Abstract Factory class as the
 * parameter, which allows the client to work with any concrete factory type.
 */
class Page
{

    public $title;

    public $content;

    public function __construct($title, $content)
    {
        $this->title = $title;
        $this->content = $content;
    }

    // Here's how would you use the template further in real life. Note that the
    // page class does not depend on any concrete template classes.
    public function render(TemplateFactory $factory): string
    {
        $pageTemplate = $factory->createPageTemplate();

        $renderer = $factory->getRenderer();

        return $renderer->render($pageTemplate->getTemplateString(), [
            'title' => $this->title,
            'content' => $this->content
        ]);
    }
}

/**
 * Now, in other parts of the app, the client code can accept factory objects of
 * any type.
 */
$page = new Page('Sample page', 'This is the body.');

echo "Testing actual rendering with the PHPTemplate factory:\n";
echo $page->render(new PHPTemplateFactory());


// Uncomment the following if you have Twig installed.

// echo "Testing rendering with the Twig factory:\n"; echo $page->render(new
// TwigTemplateFactory());

tài liệu tham khảo:

https://refactoring.guru/design-patterns/abstract-factory