当前位置:  首页>> 技术小册>> 经典设计模式PHP版

抽象工厂模式

是一种创建型设计模式,它能创建一系列相关的对象,而无需指定其具体类。


假设你正在开发一款家具商店模拟器。你的代码中包括一些 类,用于表示:

  1. 一系列相关产品, 例如 椅子 Chair 、 沙发 Sofa 和 咖啡桌 CoffeeTable 。
  2. 系列产品的不同变体。 例如, 你可以使用 现代 Modern 、 维多利亚 Victorian 、 装饰风艺术 ArtDeco 等风格生成 椅子 、 沙发 和 咖啡桌 。

你需要设法单独生成每件家具对象,这样才能确保其风格一 致。如果顾客收到的家具风格不一样,他们可不会开心。

此外, 你也不希望在添加新产品或新风格时修改已有代码。 家具供应商对于产品目录的更新非常频繁,你不会想在每次 更新时都去修改核心代码的。


解决方案

首先,抽象工厂模式建议为系列中的每件产品明确声明接口 (例如椅子、沙发或咖啡桌)。然后,确保所有产品变体都继 承这些接口。例如,所有风格的椅子都实现 椅子 接口;所 有风格的咖啡桌都实现 咖啡桌 接口,以此类推。

接下来,我们需要声明抽象工厂——包含系列中所有产品构 造方法的接口。例如 createChair 创建椅子 、 createSofa 创建沙发 和 createCoffeeTable 创建咖啡桌 。 这些方法必 须返回抽象产品类型,即我们之前抽取的那些接口: 椅子 , 沙发 和 咖啡桌 等等。

那 么 该 如 何 处 理 产 品 变 体 呢? 对 于 系 列 产 品 的 每 个 变 体, 我 们 都 将 基 于 抽象工厂 接 口 创 建 不 同 的 工 厂 类。 每 个 工 厂 类 都 只 能 返 回 特 定 类 别 的 产 品, 例 如, 现代家具工厂 ModernFurnitureFactory 只 能 创 建 现代椅子 ModernChair 、 现代沙发 ModernSofa 和 现代咖啡桌 ModernCoffeeTable 对象。
 
客户端代码可以通过相应的抽象接口调用工厂和产品类。你 无需修改实际客户端代码,就能更改传递给客户端的工厂类, 也能更改客户端代码接收的产品变体。

假设客户端想要工厂创建一把椅子。客户端无需了解工厂类, 也不用管工厂类创建出的椅子类型。无论是现代风格,还是 维多利亚风格的椅子,对于客户端来说没有分别,它只需调 用抽象 椅子 接口就可以了。这样一来,客户端只需知道椅 子以某种方式实现了 sitOn 坐下 方法就足够了。此外,无 论工厂返回的是何种椅子变体,它都会和由同一工厂对象创 建的沙发或咖啡桌风格一致。
 
最后一点说明:如果客户端仅接触抽象接口,那么谁来创建 实际的工厂对象呢?一般情况下,应用程序会在初始化阶段 创建具体工厂对象。而在此之前,应用程序必须根据配置文 件或环境设定选择工厂类别。


  1. 抽象产品(Abstract Product)为构成系列产品的一组不同但 相关的产品声明接口。
  2. 具体产品(Concrete Product)是抽象产品的多种不同类型实 现。所有变体(维多利亚/现代)都必须实现相应的抽象产品 (椅子/沙发)。
  3. 抽象工厂(Abstract Factory)接口声明了一组创建各种抽象 产品的方法。
  4. 具体工厂(Concrete Factory)实现抽象工厂的构建方法。每 个具体工厂都对应特定产品变体,且仅创建此种产品变体。
  5. 尽管具体工厂会对具体产品进行初始化,其构建方法签名必 须返回相应的抽象产品。这样,使用工厂类的客户端代码就 不会与工厂创建的特定产品变体耦合。客户端(Client)只 需通过抽象接口调用工厂和产品对象,就能与任何具体工厂/ 产品变体交互。

代码示例

  1. <?php
  2. /**
  3. * Abstract Factory Design Pattern
  4. *
  5. * Intent: Lets you produce families of related objects without specifying their
  6. * concrete classes.
  7. *
  8. * Example: In this example, the Abstract Factory pattern provides an
  9. * infrastructure for creating various types of templates for different elements
  10. * of a web page.
  11. *
  12. * A web application can support different rendering engines at the same time,
  13. * but only if its classes are independent of the concrete classes of rendering
  14. * engines. Hence, the application's objects must communicate with template
  15. * objects only via their abstract interfaces. Your code should not create the
  16. * template objects directly, but delegate their creation to special factory
  17. * objects. Finally, your code should not depend on the factory objects either
  18. * but, instead, should work with them via the abstract factory interface.
  19. *
  20. * As a result, you will be able to provide the app with the factory object that
  21. * corresponds to one of the rendering engines. All templates, created in the
  22. * app, will be created by that factory and their type will match the type of
  23. * the factory. If you decide to change the rendering engine, you'll be able to
  24. * pass a new factory to the client code, without breaking any existing code.
  25. */
  26. /**
  27. * The Abstract Factory interface declares creation methods for each distinct
  28. * product type.
  29. */
  30. interface TemplateFactory
  31. {
  32. public function createTitleTemplate(): TitleTemplate;
  33. public function createPageTemplate(): PageTemplate;
  34. public function getRenderer(): TemplateRenderer;
  35. }
  36. /**
  37. * Each Concrete Factory corresponds to a specific variant (or family) of
  38. * products.
  39. *
  40. * This Concrete Factory creates Twig templates.
  41. */
  42. class TwigTemplateFactory implements TemplateFactory
  43. {
  44. public function createTitleTemplate(): TitleTemplate
  45. {
  46. return new TwigTitleTemplate();
  47. }
  48. public function createPageTemplate(): PageTemplate
  49. {
  50. return new TwigPageTemplate($this->createTitleTemplate());
  51. }
  52. public function getRenderer(): TemplateRenderer
  53. {
  54. return new TwigRenderer();
  55. }
  56. }
  57. /**
  58. * And this Concrete Factory creates PHPTemplate templates.
  59. */
  60. class PHPTemplateFactory implements TemplateFactory
  61. {
  62. public function createTitleTemplate(): TitleTemplate
  63. {
  64. return new PHPTemplateTitleTemplate();
  65. }
  66. public function createPageTemplate(): PageTemplate
  67. {
  68. return new PHPTemplatePageTemplate($this->createTitleTemplate());
  69. }
  70. public function getRenderer(): TemplateRenderer
  71. {
  72. return new PHPTemplateRenderer();
  73. }
  74. }
  75. /**
  76. * Each distinct product type should have a separate interface. All variants of
  77. * the product must follow the same interface.
  78. *
  79. * For instance, this Abstract Product interface describes the behavior of page
  80. * title templates.
  81. */
  82. interface TitleTemplate
  83. {
  84. public function getTemplateString(): string;
  85. }
  86. /**
  87. * This Concrete Product provides Twig page title templates.
  88. */
  89. class TwigTitleTemplate implements TitleTemplate
  90. {
  91. public function getTemplateString(): string
  92. {
  93. return "<h1>{{ title }}</h1>";
  94. }
  95. }
  96. /**
  97. * And this Concrete Product provides PHPTemplate page title templates.
  98. */
  99. class PHPTemplateTitleTemplate implements TitleTemplate
  100. {
  101. public function getTemplateString(): string
  102. {
  103. return "<h1><?= \$title; ?></h1>";
  104. }
  105. }
  106. /**
  107. * This is another Abstract Product type, which describes whole page templates.
  108. */
  109. interface PageTemplate
  110. {
  111. public function getTemplateString(): string;
  112. }
  113. /**
  114. * The page template uses the title sub-template, so we have to provide the way
  115. * to set it in the sub-template object. The abstract factory will link the page
  116. * template with a title template of the same variant.
  117. */
  118. abstract class BasePageTemplate implements PageTemplate
  119. {
  120. protected $titleTemplate;
  121. public function __construct(TitleTemplate $titleTemplate)
  122. {
  123. $this->titleTemplate = $titleTemplate;
  124. }
  125. }
  126. /**
  127. * The Twig variant of the whole page templates.
  128. */
  129. class TwigPageTemplate extends BasePageTemplate
  130. {
  131. public function getTemplateString(): string
  132. {
  133. $renderedTitle = $this->titleTemplate->getTemplateString();
  134. return <<<HTML
  135. <div class="page">
  136. $renderedTitle
  137. <article class="content">{{ content }}</article>
  138. </div>
  139. HTML;
  140. }
  141. }
  142. /**
  143. * The PHPTemplate variant of the whole page templates.
  144. */
  145. class PHPTemplatePageTemplate extends BasePageTemplate
  146. {
  147. public function getTemplateString(): string
  148. {
  149. $renderedTitle = $this->titleTemplate->getTemplateString();
  150. return <<<HTML
  151. <div class="page">
  152. $renderedTitle
  153. <article class="content"><?= \$content; ?></article>
  154. </div>
  155. HTML;
  156. }
  157. }
  158. /**
  159. * The renderer is responsible for converting a template string into the actual
  160. * HTML code. Each renderer behaves differently and expects its own type of
  161. * template strings passed to it. Baking templates with the factory let you pass
  162. * proper types of templates to proper renders.
  163. */
  164. interface TemplateRenderer
  165. {
  166. public function render(string $templateString, array $arguments = []): string;
  167. }
  168. /**
  169. * The renderer for Twig templates.
  170. */
  171. class TwigRenderer implements TemplateRenderer
  172. {
  173. public function render(string $templateString, array $arguments = []): string
  174. {
  175. return \Twig::render($templateString, $arguments);
  176. }
  177. }
  178. /**
  179. * The renderer for PHPTemplate templates. Note that this implementation is very
  180. * basic, if not crude. Using the `eval` function has many security
  181. * implications, so use it with caution in real projects.
  182. */
  183. class PHPTemplateRenderer implements TemplateRenderer
  184. {
  185. public function render(string $templateString, array $arguments = []): string
  186. {
  187. extract($arguments);
  188. ob_start();
  189. eval(' ?>' . $templateString . '<?php ');
  190. $result = ob_get_contents();
  191. ob_end_clean();
  192. return $result;
  193. }
  194. }
  195. /**
  196. * The client code. Note that it accepts the Abstract Factory class as the
  197. * parameter, which allows the client to work with any concrete factory type.
  198. */
  199. class Page
  200. {
  201. public $title;
  202. public $content;
  203. public function __construct($title, $content)
  204. {
  205. $this->title = $title;
  206. $this->content = $content;
  207. }
  208. // Here's how would you use the template further in real life. Note that the
  209. // page class does not depend on any concrete template classes.
  210. public function render(TemplateFactory $factory): string
  211. {
  212. $pageTemplate = $factory->createPageTemplate();
  213. $renderer = $factory->getRenderer();
  214. return $renderer->render($pageTemplate->getTemplateString(), [
  215. 'title' => $this->title,
  216. 'content' => $this->content
  217. ]);
  218. }
  219. }
  220. /**
  221. * Now, in other parts of the app, the client code can accept factory objects of
  222. * any type.
  223. */
  224. $page = new Page('Sample page', 'This it the body.');
  225. echo "Testing actual rendering with the PHPTemplate factory:\n";
  226. echo $page->render(new PHPTemplateFactory());
  227. // Uncomment the following if you have Twig installed.
  228. // echo "Testing rendering with the Twig factory:\n"; echo $page->render(new
  229. // TwigTemplateFactory());

该分类下的相关小册推荐: