#### 抽象工廠模式(Abstract Factory)
1、模式概述
抽象工廠模式為一組相關或相互依賴的對象創建提供接口,而無需指定其具體實現類。抽象工廠的客戶端不關心如何創建這些對象,只關心如何將它們組合到一起。
2、問題引出
舉個例子,如果某個應用是可移植的,那么它需要封裝平臺依賴,這些平臺可能包括窗口系統、操作系統、數據庫等等。這種封裝如果未經設計,通常代碼會包含多個 if 條件語句以及對應平臺的操作。這種硬編碼不僅可讀性差,而且擴展性也不好。
3、解決方案
提供一個間接的層(即“抽象工廠”)抽象一組相關或依賴對象的創建而不是直接指定具體實現類。該“工廠”對象的職責是為不同平臺提供創建服務。客戶端不需要直接創建平臺對象,而是讓工廠去做這件事。
這種機制讓替換平臺變得簡單,因為抽象工廠的具體實現類只有在實例化的時候才出現,如果要替換的話只需要在實例化的時候指定具體實現類即可。
4、UML類圖
抽象工廠為每個產品(具體實現)定義了工廠方法,每個工廠方法封裝了new操作符和具體類(指定平臺的產品類),每個“平臺”都是抽象工廠的派生類。

5、代碼實現
AbstractFactory.php
~~~
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* 抽象工廠類
*
* 該設計模式實現了設計模式的依賴倒置原則,因為最終由具體子類創建具體組件
*
* 在本例中,抽象工廠為創建 Web 組件(產品)提供了接口,這里有兩個組件:文本和圖片,有兩種渲染方式:HTML
* 和 JSON,對應四個具體實現類。
*
* 盡管有四個具體類,但是客戶端只需要知道這個接口可以用于構建正確的 HTTP 響應即可,無需關心其具體實現。
*/
abstract class AbstractFactory
{
/**
* 創建本文組件
*
* @param string $content
*
* @return Text
*/
abstract public function createText($content);
/**
* 創建圖片組件
*
* @param string $path
* @param string $name
*
* @return Picture
*/
abstract public function createPicture($path, $name = '');
}
~~~
JsonFactory.php
~~~
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* JsonFactory類
*
* JsonFactory 是用于創建 JSON 組件的工廠
*/
class JsonFactory extends AbstractFactory
{
/**
* 創建圖片組件
*
* @param string $path
* @param string $name
*
* @return Json\Picture|Picture
*/
public function createPicture($path, $name = '')
{
return new Json\Picture($path, $name);
}
/**
* 創建文本組件
*
* @param string $content
*
* @return Json\Text|Text
*/
public function createText($content)
{
return new Json\Text($content);
}
}
~~~
HtmlFactory.php
~~~
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* HtmlFactory類
*
* HtmlFactory 是用于創建 HTML 組件的工廠
*/
class HtmlFactory extends AbstractFactory
{
/**
* 創建圖片組件
*
* @param string $path
* @param string $name
*
* @return Html\Picture|Picture
*/
public function createPicture($path, $name = '')
{
return new Html\Picture($path, $name);
}
/**
* 創建文本組件
*
* @param string $content
*
* @return Html\Text|Text
*/
public function createText($content)
{
return new Html\Text($content);
}
}
~~~
MediaInterface.php
~~~
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* MediaInterface接口
*
* 該接口不是抽象工廠設計模式的一部分, 一般情況下, 每個組件都是不相干的
*/
interface MediaInterface
{
/**
* JSON 或 HTML(取決于具體類)輸出的未經處理的渲染
*
* @return string
*/
public function render();
}
~~~
Picture.php
~~~
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* Picture類
*/
abstract class Picture implements MediaInterface
{
/**
* @var string
*/
protected $path;
/**
* @var string
*/
protected $name;
/**
* @param string $path
* @param string $name
*/
public function __construct($path, $name = '')
{
$this->name = (string) $name;
$this->path = (string) $path;
}
}
~~~
Text.php
~~~
<?php
namespace DesignPatterns\Creational\AbstractFactory;
/**
* Text類
*/
abstract class Text implements MediaInterface
{
/**
* @var string
*/
protected $text;
/**
* @param string $text
*/
public function __construct($text)
{
$this->text = (string) $text;
}
}
~~~
Json/Picture.php
~~~
<?php
namespace DesignPatterns\Creational\AbstractFactory\Json;
use DesignPatterns\Creational\AbstractFactory\Picture as BasePicture;
/**
* Picture類
*
* 該類是以 JSON 格式輸出的具體圖片組件類
*/
class Picture extends BasePicture
{
/**
* JSON 格式輸出
*
* @return string
*/
public function render()
{
return json_encode(array('title' => $this->name, 'path' => $this->path));
}
}
~~~
Json/Text.php
~~~
<?php
namespace DesignPatterns\Creational\AbstractFactory\Json;
use DesignPatterns\Creational\AbstractFactory\Text as BaseText;
/**
* Class Text
*
* 該類是以 JSON 格式輸出的具體文本組件類
*/
class Text extends BaseText
{
/**
* 以 JSON 格式輸出的渲染
*
* @return string
*/
public function render()
{
return json_encode(array('content' => $this->text));
}
}
~~~
Html/Picture.php
~~~
<?php
namespace DesignPatterns\Creational\AbstractFactory\Html;
use DesignPatterns\Creational\AbstractFactory\Picture as BasePicture;
/**
* Picture 類
*
* 該類是以 HTML 格式渲染的具體圖片類
*/
class Picture extends BasePicture
{
/**
* HTML 格式輸出的圖片
*
* @return string
*/
public function render()
{
return sprintf('<img src="%s" title="%s"/>', $this->path, $this->name);
}
}
~~~
Html/Text.php
~~~
<?php
namespace DesignPatterns\Creational\AbstractFactory\Html;
use DesignPatterns\Creational\AbstractFactory\Text as BaseText;
/**
* Text 類
*
* 該類是以 HTML 渲染的具體文本組件類
*/
class Text extends BaseText
{
/**
* HTML 格式輸出的文本
*
* @return string
*/
public function render()
{
return '<div>' . htmlspecialchars($this->text) . '</div>';
}
}
~~~
6、測試代碼
Tests/AbstractFactoryTest.php
~~~
<?php
namespace DesignPatterns\Creational\AbstractFactory\Tests;
use DesignPatterns\Creational\AbstractFactory\AbstractFactory;
use DesignPatterns\Creational\AbstractFactory\HtmlFactory;
use DesignPatterns\Creational\AbstractFactory\JsonFactory;
/**
* AbstractFactoryTest 用于測試具體的工廠
*/
class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
{
public function getFactories()
{
return array(
array(new JsonFactory()),
array(new HtmlFactory())
);
}
/**
* 這里是工廠的客戶端,我們無需關心傳遞過來的是什么工廠類,
* 只需以我們想要的方式渲染任意想要的組件即可。
*
* @dataProvider getFactories
*/
public function testComponentCreation(AbstractFactory $factory)
{
$article = array(
$factory->createText('Laravel學院'),
$factory->createPicture('/image.jpg', 'laravel-academy'),
$factory->createText('LaravelAcademy.org')
);
$this->assertContainsOnly('DesignPatterns\Creational\AbstractFactory\MediaInterface', $article);
}
}
~~~
執行測試:
~~~
phpunit /path/to/AbstractFactoryTest.php
~~~
7、總結
最后我們以工廠生產產品為例,所謂抽象工廠模式就是我們的抽象工廠約定了可以生產的產品,這些產品都包含多種規格,然后我們可以從抽象工廠為每一種規格派生出具體工廠類,然后讓這些具體工廠類生產具體的產品。
以上示例中AbstractFactory是抽象工廠,JsonFactory和HtmlFactory是具體工廠,Html\Picture、Html\Text、Json\Picture和Json\Text都是具體產品,客戶端需要HTML格式的Text,調用HtmlFactory的createText方法即可,而不必關心其實現邏輯。