抽象工厂模式(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
方法即可,而不必关心其实现逻辑。
36 Comments
PHPUnit 的单元测试基类 所有单元测试类都必须直接或间接继承自该类
phpunit命令完整目录为vender/bin/phpunit
测试代码的extends \PHPUnit_Framework_TestCase,高版本的phpunit是不要应该改成 extends \PHPUnit\Framework\TestCase,我用的phpunit版本是6.5.3,用前面的,会提示报错,Class 'PHPUnit_Framework_TestCase' not found
1、如果在没有使用框架的情况下需要include 或 require 命名空间所在的文件
namespace work\test; require_once('../HtmlFactory.php'); require_once('../JsonFactory.php'); require_once('../AbstractFactory.php'); use work\HtmlFactory; use work\JsonFactory; use work\AbstractFactory; use PHPUnit\Framework\TestCase; class FactoryTest extends TestCase { function getFactories(){ return array( array(new HtmlFactory()), array(new JsonFactory()) ); }
} 2、设计模式中 使用PHPunit测试基类想说明什么? 准确的来说 抽象工厂是给客户提供不同的具象产物的生产车间(具象工厂) 因此 工厂有很多规格与否这个客户端不关心,当客户端需要json的text 或者 picture时 他只需要通过具象工厂生产就行。和PHPunit生成的测试结果 没有任何关系,而且在这个地方使用phpunit本身容易跑偏,看了一下评论,大多数人都不太明白这是个什么玩意,新手在学设计模式的时候又需要去学phpunit 没有必要 ,而且在工作中 大多数情况下未必会使用phpunit测试用例! 附phpunit测试用例结果(丝毫没有毛用): ▶ phpunit ./FactoryTest.php PHPUnit 6.5.3 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)
Time: 91 ms, Memory: 8.00MB
OK (2 tests, 2 assertions)
testComponentCreation直接使用AbstractFactory妥吗?createText,createPicture可以顺利找到吗?这个测试用例有点没看懂~
render函数好像没有用到~测试用例assertContainsOnly只判断类型的话31行和33行的调用有点看不懂~调用createText的时候,AbstractFactory的实现类是哪个呢?应该是分开测试吗~