抽象工厂
意图
《设计模式》对抽象工厂 (Abstract Factory) 意图的描述如下
提供一个接口以创建一系列相关或相互依赖的对象,而无须指定它们具体的类。
在项目的初期,往往会使用工厂方法来创建单个对象。
<?php
abstract class AbstractFactory
{
abstract public function factoryMethod() : AbstractProduct;
}
class ConcreteFactory extends AbstractFactory
{
public function factoryMethod(): ConcreteProduct
{
return new ConcreteProduct();
}
}
但是随着项目的复杂度增加,可能需要同时创建多个相互关联的对象,这时候就可以考虑使用抽象工厂。
<?php
interface AbstractFactory
{
public function createProductA(): AbstractProductA;
public function createProductB(): AbstractProductB;
}
class ConcreteFactory implements AbstractFactory
{
public function createProductA(): AbstractProductA
{
return new ConcreteProductA1;
}
public function createProductB(): AbstractProductB
{
return new ConcreteProductB1;
}
}
通过对比,可以看出抽象工厂就像是工厂方法的横向扩展。所以抽象工厂具备工厂方法的所有优点,符合单一职责、开放-封闭等设计原则。不同之处就在于抽象工厂要要入更多的接口和类,增加了代码的复杂性。
我们举一个生活中的例子,当我们定制家具的时候,是按照风格选择的,北欧风格的抽象工厂会定义北欧风格的椅子、桌子、沙发,而中式风格的抽象工厂则会定义中式风格的椅子、桌子、沙发,通过抽象工厂,既不会出现家具风格搭配出错的情况。
再举一个开发中的例子,一个 UI 的抽象工厂类有两个子类,分别对应两个 UI 风格,一套是 Material 风格,一套是 Bootstrap 风格。通过抽象工厂,就可以管理这两套不同风格的 UI,并保证它们的风格一致。对于客户端来说,只需要传入抽象的 UI 类就行了,然后在页面中使用按钮或者表单组件即可。至于使用哪套风格,只需要在项目初始化的时候进行配置即可。并且,你可以在不改动原来代码的情况下添加新的 UI 风格,唯一麻烦的就是要创建较多的类。
实现
抽象工厂接口声明了一组创建各种抽象产品的方法
<?php
interface AbstractFactory
{
public function createProductA(): AbstractProductA;
public function createProductB(): AbstractProductB;
}
而每一个具体产品工厂则创建具体的产品组合
<?php
// 产品组合 1
class ConcreteFactory1 implements AbstractFactory
{
public function createProductA(): AbstractProductA
{
return new ConcreteProductA1;
}
public function createProductB(): AbstractProductB
{
return new ConcreteProductB1;
}
}
// 产品组合 2
class ConcreteFactory2 implements AbstractFactory
{
public function createProductA(): AbstractProductA
{
return new ConcreteProductA2;
}
public function createProductB(): AbstractProductB
{
return new ConcreteProductB2;
}
}
抽象产品接口以及具体的产品实现
<?php
interface AbstractProductA
{
public function usefulFunctionA(): string;
}
class ConcreteProductA1 implements AbstractProductA
{
public function usefulFunctionA(): string
{
return "产品 A1.";
}
}
class ConcreteProductA2 implements AbstractProductA
{
public function usefulFunctionA(): string
{
return "产品 A2.";
}
}
interface AbstractProductB
{
public function usefulFunctionB(): string;
}
class ConcreteProductB1 implements AbstractProductB
{
public function usefulFunctionB(): string
{
return "产品 B1.";
}
}
class ConcreteProductB2 implements AbstractProductB
{
public function usefulFunctionB(): string
{
return "产品 B2.";
}
}
最后,客户端仅需要使用抽象工厂接口,应用在初始化的时候可以根据配置文件来决定使用哪个工厂来进行创建。
<?php
function clientCode(AbstractFactory $factory)
{
$productA = $factory->createProductA();
$productB = $factory->createProductB();
}
clientCode(new ConcreteFactory1());
应用
PHP 的模板语言可以用抽象工厂模式实现。模板引擎可以将应用界面和数据进行分离,对于 PHP 而言,存在多个模板引擎,比如 Twig、Blade 等等。当我们在页面中使用模板语言时,我们只需要知道用法即可,不需要知道模板语言是如何转化成 HTML 代码的。
定义模板引擎的抽象接口(抽象工厂类)
<?php
// 抽象工厂类 - 管理不同的模板子类
interface TemplateFactory
{
// 创建标题子模板
public function createTitleTemplate(): TitleTemplate;
// 创建页面子模板
public function createPageTemplate(): PageTemplate;
// 将模板语言转化为 HTML 代码
public function getRenderer(): TemplateRenderer;
}
Twig 模板工厂,用来创建一整套 Twig 模板
<?php
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();
}
}
每个子产品的定义及实现
<?php
// 标题子模板接口
interface TitleTemplate
{
public function getTemplateString(): string;
}
class TwigTitleTemplate implements TitleTemplate
{
public function getTemplateString(): string
{
return "<h1>{{ title }}</h1>";
}
}
// 页面子模板
interface PageTemplate
{
public function getTemplateString(): string;
}
abstract class BasePageTemplate implements PageTemplate
{
protected $titleTemplate;
public function __construct(TitleTemplate $titleTemplate)
{
$this->titleTemplate = $titleTemplate;
}
}
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;
}
}
interface TemplateRenderer
{
public function render(string $templateString, array $arguments = []): string;
}
class TwigRenderer implements TemplateRenderer
{
public function render(string $templateString, array $arguments = []): string
{
return \Twig::render($templateString, $arguments);
}
}
我们定义一个 Page 类,来管理页面的渲染。在该类中,我们只需要传入所需要的变量,并调用对应的方法即可,完全不需要考虑它们是如何变成 HTML 语言的。
<?php
class Page
{
public $title;
public $content;
public function __construct($title, $content)
{
$this->title = $title;
$this->content = $content;
}
public function render(TemplateFactory $factory): string
{
$pageTemplate = $factory->createPageTemplate();
$renderer = $factory->getRenderer();
return $renderer->render($pageTemplate->getTemplateString(), [
'title' => $this->title,
'content' => $this->content
]);
}
}
$page = new Page('Sample page', 'This it the body.');
echo $page->render(new TwigTemplateFactory());
总结
在设计良好的程序中, 每个类仅负责一件事,在设计应用的时候,可以用抽象工厂将这些关联的类管理起来,以保证未来功能实现的一致性。