工厂模式
在讲解工厂模式之前,我们先来探讨一些问题,研究是为什么会出现工厂模式的,工厂模式有什么优缺点。
以超人为例子:有一个超人,超人一定有对种超能力;
于是我们建立一个超人类
namespace Factory;
class Superman
{
}
同时创建超能力类并给超人添加超能力;
namespace Factory;
class Flight
{
/**
* 能力的名称
*
* @var
*/
protected $name;
/**
* 能力值
*
* @var
*/
protected $ability;
public function __construct($name, $ability)
{
$this->name = $name;
$this->ability = $ability;
}
}
class Force
{
//该类的属性和方法
}
class Jog
{
//该类的属性和方法
}
给超人添加超能力
namespace Factory;
class Superman
{
public function __construct()
{
$power_class = new Power('超能力', 1000);
$force_class = new Force();
$jog_class = new Jog();
}
}
通过上面的代码可以得到:
- 超人要添加一个超能力就必须new个能力类
- 如果能力类的参数需要修改,那么实例化的参数要需要修改
- 能力类的名称觉得不合理,那么调用的代码也要修改
如果上面的修改出现在多个不同的地方,那么我们就会花费很多时间或精力去修改这个东西。所以我们可以引出工厂模式,去解决这些问题。
简单工厂模式
问题1:
- 前提: 在超人的超能力不多的情况下(只有一两个已知的)
- 懒: 不想在每次添加添加超能力的时候,写new
- 超能力(服务端)不想让超人(client)知道有哪些超能力类--面向接口
解决方案:简单工厂模式
- 首先创建工厂
namespace Factory;
class SimpleFactory
{
protected $power_type = [
'force' => __NAMESPACE__ . '\Force',
];
/**
* 添加超能力
* @param string $type 超能力类型key
* @return 超能力的实例化对象
* @throws \Exception
*/
public function addPower($type)
{
if (!array_key_exists($type, $this->power_type)) {
throw new \Exception("超能力不存在");
}
$class_name = $this->power_type[$type];
return new $class_name;
}
}
- 超能力
namespace Factory;
/**
* 具体超能力类
* Class Force
* @package Factory
*/
class Force extends SimpleFactory
{
public function setPower()
{
echo "超能力:力量" . PHP_EOL;
}
}
- 用法
<?php
spl_autoload_register(function($class_name) {
$class_file = realpath(dirname(__FILE__)."/../") .'/' . str_replace('\\','/', $class_name) . '.php';
if (file_exists($class_file)) {
include $class_file;
}
});
try {
(new SimpleFactory())->addPower('force')->setPower();
} catch (Exception $e) {
echo $e->getMessage();
}
总结
- 使用前提:当工厂知道所有要生产的类型(子类比较少),可以使用简单工厂模式。
- 优点:可以使用户根据获得对应类类型,避免了直接实例化类,降低耦合度(面向接口开发:客户端不用知道服务的有哪些类,只要知道类型就可以了);
- 缺点:可实例化的类型在编辑期间已经被确定,如果增加新类型,则需要修改工厂,不符合开闭原则。
静态工厂模式
问题2: 如果不想在调用的时候new容器
解决:使用静态工厂模式
- 使用该模式现在我们只要对工厂类的代码修改一下
namespace Factory;
class SimpleFactory
{
protected $power_type = [
'force' => __NAMESPACE__ . '\Force',
];
/**
* 添加超能力(将方法改成静态)
* @param string $type 超能力类型key
* @return 超能力的实例化对象
* @throws \Exception
*/
public static function addPower($type)
{
if (!array_key_exists($type, self::power_type)) {
throw new \Exception("超能力不存在");
}
$class_name = self::power_type[$type];
return new $class_name;
}
}
总结
与简单工厂类似,该模式用于创建一组相关或依赖的对象,不同之处在于静态工厂模式使用一个静态方法来创建所有类型的对象,该静态方法通常是 factory 或 build。
因为上面的超能力只有一个,所以不会出现调用超能力方法不一样的情况;如果现在超能力有多个的化,我们最后规范一下超能力调用方法接口。因此引入超能力的接口类。(也可以理解成面向接口开发,这样别人就能够更加方便调用)
- 增加能力接口类
namespace Factory;
/**
* 超能力接口
* Interface PowerInterface
* @package Factory
*/
interface PowerInterface
{
public function setPower();
}
- 对超能力类改造并增加一个超能力类
namespace Factory;
class Force implements PowerInterface
{
public function setPower()
{
echo "超能力:力量" . PHP_EOL;
}
}
namespace Factory;
class Jog implements PowerInterface
{
public function setPower()
{
echo "超能力:跑步";
}
}
- 对简单工厂类优化一下
namespace Factory;
class SimpleFactory
{
protected $module; //超能力类对象
/**
* 这里使用依赖注入的方式
* SimpleFactory constructor.
* @param PowerInterface $type
*/
public function __construct(PowerInterface $type)
{
$this->module = $type;
}
/**
* 添加超能力类
*/
public function addPower()
{
$this->module->setPower();
}
}
抽象工厂模式
问题3:
- 上面超能力和工厂比较少,如果现在要一下生产大量并且具有多种超能力的超人。
- 如果上一个方法没有对工厂代码使用依赖注入的话,添加一种超能力还得在超能力类型变量种添加一个参数;这样的违反开闭原则(可以扩张,不能修改);
解决:
- 这里我们利用多个工厂——抽象工厂模式,定义一个抽象工厂,其他的在此上面扩展,就不用修改原来的代码。也不会违反开闭原则。
- 生产多种超人——接口规范超人能力调用,防止不一样
首先对工厂进行操作
- 创建一个工厂方法抽象类FactoryMethod.php,用来添加超能力
namespace Factory;
/**
* 工厂方法抽象类
* Class FactoryMethod
* @package Factory
*/
abstract class FactoryMethod
{
const JOG = 1;
const FORCE = 2;
/**
* 子类实现启动超能力方法
* @param $type
* @return mixed
*/
abstract protected function activate($type);
/**
* 添加超能力
* @param $type
* @return PowerInterface a new power
*/
public function addPower($type)
{
$obj = $this->activate($type);
$obj->setPower();
return $obj;
}
}
- 添加工厂1(FactoryOne.php)
namespace Factory;
use Exception;
/**
* 工厂类1
* Class FactoryOne
* @package Factory
*/
class FactoryOne extends FactoryMethod
{
protected function activate($type)
{
switch ($type) {
case parent::JOG :
return new Jog();
break;
case parent::FORCE :
return new Force();
break;
default :
throw new Exception("this type is not a valid power");
}
}
}
- 添加工厂2(FactoryTwo.php)
namespace Factory;
use Exception;
/**
* 工厂类2
* Class FactoryTwo
* @package Factory
*/
class FactoryTwo extends FactoryMethod
{
public function activate($type)
{
switch ($type) {
case parent::JOG :
return new Jog();
break;
case parent::FORCE :
return new Force();
break;
default :
throw new Exception("this type is not a valid power");
}
}
}
工厂1和工厂2继承工厂方法抽象类,实现创建超能力(工厂里边存放实现超能力的对象),所以工厂实际上就是,把创建对象的过程封装起来,随时可以产生一个新的对象。
然后就是对超能力的操作
- 创建一个超能力的接口类PowerInterface.php(利用接口规范超能力)
namespace Factory;
/**
* 超能力接口
* Interface PowerInterface
* @package Factory
*/
interface PowerInterface
{
public function setPower();
}
- 超能力1
namespace Factory;
class Force implements PowerInterface
{
public function setPower()
{
echo "超能力:力量" . PHP_EOL;
}
}
- 超能力2
namespace Factory;
class Jog implements PowerInterface
{
public function setPower()
{
echo "超能力:跑步";
}
}
到这里工厂方法模式已经完成了,现在我们先继续把创建超人的代码完成。
最后创建超人类Superman.php
namespace Factory;
class Superman
{
protected $module;
/**
* Superman constructor.
* @param FactoryMethod $factory
*/
public function __construct(FactoryMethod $factory)
{
$this->module = $factory;
}
/**
* 创建具有某种超能力的超人
* @param $type
*/
public function addSuperman($type)
{
$this->module->addPower($type);
}
}
现在我们创建超人就非常方便了
<?php
use Factory\FactoryOne;
use Factory\FactoryTwo;
use Factory\Superman;
spl_autoload_register(function($class_name) {
$class_file = realpath(dirname(__FILE__)."/../") .'/' . str_replace('\\','/', $class_name) . '.php';
if (file_exists($class_file)) {
include $class_file;
}
});
//利用工厂1 创建超人(超能力是快跑)
$superman_1 = new Superman(new FactoryOne());
$superman_1->addSuperman(1);
//利用工厂2 创建超人(超能力是大力)
$superman_2 = new Superman(new FactoryTwo());
$superman_2->addSuperman(2);
定义多个工厂,每个工厂生产不同的超人的超能力实例,支持增加超能力类型,或工厂生产哪中超能力。
创建超人的时候,我们不用关注工厂是如何创建超人的,我们只关注工厂(工厂抽象方法类)提供的接口就可以了。这样看来工厂就是将创造超能力(多个对象)的过程封装起来
总结
工厂方法针对每一种产品提供一个工厂类,通过不同的工厂实例来创建不同的产品实例,在同一等级结构中,支持增加任意产品(或者我可以把他理解成:将创建对象的过程封装起来,这样对于外部关注抽象类,而不是具体的类)。这个模式同时也实现了依赖倒置。
我们经常在数据库的联系、支付接口或者用户工厂的时候使用工厂模式。比如未来可能对应不同的支付网关:支付宝、财付通、网银在线等。方便未来扩展,设计成工厂模式。定一个专门生产网关接口的工厂,抽象出来,做成接口形式,让所有的子类都要实现它的接口。以后加一个支付方式,要使用哪一种支付方式,改变一下参数即可。