PHP设计模式-几种工厂模式的对比

工厂模式

在讲解工厂模式之前,我们先来探讨一些问题,研究是为什么会出现工厂模式的,工厂模式有什么优缺点。

以超人为例子:有一个超人,超人一定有对种超能力;
于是我们建立一个超人类

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);

定义多个工厂,每个工厂生产不同的超人的超能力实例,支持增加超能力类型,或工厂生产哪中超能力。

创建超人的时候,我们不用关注工厂是如何创建超人的,我们只关注工厂(工厂抽象方法类)提供的接口就可以了。这样看来工厂就是将创造超能力(多个对象)的过程封装起来

uml图片可以参考这个

总结

工厂方法针对每一种产品提供一个工厂类,通过不同的工厂实例来创建不同的产品实例,在同一等级结构中,支持增加任意产品(或者我可以把他理解成:将创建对象的过程封装起来,这样对于外部关注抽象类,而不是具体的类)。这个模式同时也实现了依赖倒置。

我们经常在数据库的联系、支付接口或者用户工厂的时候使用工厂模式。比如未来可能对应不同的支付网关:支付宝、财付通、网银在线等。方便未来扩展,设计成工厂模式。定一个专门生产网关接口的工厂,抽象出来,做成接口形式,让所有的子类都要实现它的接口。以后加一个支付方式,要使用哪一种支付方式,改变一下参数即可。

文章整理参考:PHP 设计模式系列 —— 工厂方法模式(Factory Method)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容