PHP设计模式之工厂模式(Factory)了解下

工厂模式的意思其实就是提供获取某个对象实例的一个接口,同时使调用代码避免确定实例化基类的步骤,实际上就是建立一个统一的类实例化的函数接口,完事统一调用,统一控制,它是PHP中常用的一种设计模式,一般会配合单例模式一起使用,来加载php类库中的类。来看一个简单的应用场景:

  1. 我们拥有一个Json类,String类,Xml类。
  2. 如果我们不使用工厂方式实例化这些类,则需要每一个类都需要new一遍,过程不可控,类多了,到处都是new的身影
  3. 引进工厂模式,通过工厂统一创建对象实例。

代码如下:

<?php
//工厂模式 提供获取某个对象实例的一个接口,同时使调用代码避免确定实例化基类的步骤
//字符串类
class String {
	public function write() {}
}
//Json类
class Json {
	public function getJsonData() {}
}
//xml类
class Xml {
	public function buildXml() {}
}
//工厂类
class Factory {
	public static function create($class) {
		return new $class;
	}
}
Factory::create("Json"); //获取Json对象

我们现在应该对于工厂模式有了一个大概的理解了,咱们接下来可以从字面上来理解一下。

工厂么,它就是生产产品的地方,它有原料,设备和产品,那么在PHP中,我们可以理解为,这个工厂模式可以通过一个工厂类(设备),来调用自身的静态方法(生产方式)来产生对象实例(产品),在上述实例中的Json类等,就相当于原料了。

理解了上面的一段话之后,我们就可以再深入的了解下这个工厂模式了。

我们来考虑以下场景,如果项目中,我们通过一个类创建对象,在快完成或者已经完成,要扩展功能的时候,发现原来的类的类名不是很合适或者发现类需要添加构造函数参数才能实现功能扩展,在这种情况下,大家就可以感受到“高内聚低耦合”的博大精深,我们可以尝试使用工厂模式解决这个问题。

还有就是最经典的数据库连接问题等等,都可以使用工厂模式来接觉问题,咱们也不废话,来看一下网上一个比较经典的案例:

interface Transport{
    public function go();

}

class Bus implements Transport{
    public function go(){
        echo "bus每一站都要停";
    }
}

class Car implements Transport{
    public function go(){
        echo "car跑的飞快";
    }
}

class Bike implements Transport{
    public function go(){
        echo "bike比较慢";
    }
}

class transFactory{
    public static function factory($transport)
    {
        
        switch ($transport) {
            case 'bus':
                return new Bus();
                break;

            case 'car':
                return new Car();
                break;
            case 'bike':
                return new Bike();
                break;
        }
    }
}

$transport=transFactory::factory('car');
$transport->go();

大家有了解过工厂模式应该都知道,工厂模式有三种,那就是一般工厂模式(静态工厂模式),工厂模式,还有就是抽象工厂模式,咱这里并未把所有的案例全部介绍完毕,不过嘞,咱们可以跟着网上的一个案例,来简单了解下工厂模式的三种变形的过程。

首先,我们来假设有个关于个人事务管理的项目,功能之一就是管理Appointment(预约)对象。我们的业务团队和A公司建立了关系,目前需要使用一个叫做BloggsCal格式来和他们交流预约相关的数据,但是业务部门提醒可能会有更多的数据格式,所以解码器可能会有多种,我们呢,为了避免在逻辑代码中使用过多的if else,可能就会需要使用工厂模式来将创造者和使用者分开。

那么,我们就需要两个类,一个类AppEncoder用于定义一个解码器,将A公司传来的数据解码;另外一个类CommsManager用于获取该解码器,就是调用AppEncoder类,用于与A公司进行通信。使用模式术语说,CommsManager就是创造者,AppEncoder就是产品(一个创造者、一个产品,将类的实例化和对象的使用分离开,这就是工厂模式的思想)。

咱们先来通过简单工厂模式实现上述任务场景,如下:

//产品类
class BloggsApptEncoder {
    function encode()
    {
        return "Appointment data encoded in BloggsCal format\n";
    }  
}

//创造者类
class CommsManager {
    function static getBloggsApptEncoder()
    { 
        return new BloggsApptEncoder();
    } 
}

大概明白了奥,好啦,现在又有新任务了,业务部门告诉我们需要新增一种数据格式MegCal,来完成数据交流,那么我们就需要新增对应的解码器类,然后直接在commsManager新增参数来标识需要实例化哪个解码器,如下:

class CommsManager {
    const BLOGGS = 1;
    const MEGA = 2;
    private $mode;

    public function __construct( $mode )
    {
        $this->mode = $mode;
    }  

    function getApptEncoder()
    {
        switch($this->mode) {
            case (self::MEGA):
                return new MegaApptEncoder();
            default:
                return new BloggsApptEncoder();
        }    
    }
}

上述两个案例综合起来就是简单工厂模式了,它符合现实中的情况,而且客户端免除了直接创建产品对象的责任,而仅仅负责“消费”产品(正如暴发户所为)。

接下来,我们从开闭原则上来分析下简单工厂模式,当新增一种数据格式的时候,只要符合抽象产品格式,那么只要通知工厂类知道就可以被使用了(即创建一个新的解码器类,继承抽象解码器ApptEncoder),那么对于产品部分来说,它是符合开闭原则的——对扩展开放、对修改关闭,但是对于工厂类不太理想,因为每增加一各格式,都要在工厂类中增加相应的商业逻辑和判断逻辑,这显自然是违背开闭原则的。

然而在实际应用中,很可能产品是一个多层次的树状结构,这时候由于简单工厂模式中只有一个工厂类来对应这些产品,所以这可能会把我们的上帝累坏了,因此简单工厂模式只适用于业务简单的情况下或者具体产品很少增加的情况,而对于复杂的业务环境可能不太适应了,这个时候就应该由工厂方法模式来出场了。

又来新需求了,那就是每种格式的预约数据中,需要提供页眉和页脚来描述每次预约。

咱们先来用简单工厂模式来实现上述功能,如下:

// 简单工厂模式
class CommsManager {
    const BLOGGS = 1;
    const MEGA = 2;
    private = $mode;

    public function __construct( $mode )
    {
        $this->mode = $mode;
    }

    // 生成解码器对应的页眉
    public function getHeaderText() 
    {
        switch( $this->mode ) {
            case ( self::MEGA ):
                return "MegaCal header\n";
            default:
                return "BloggsCal header\n";
        }
    }
   
    // 生成解码器
    public function getApptEncoder()
    {
        switch( $this->mode ) {
            case ( self::MEGA ):
                return new MegaApptEncoder();
            default:
                return new BloggsApptEncoder();;
        }    
    }
}

从上述代码中,我们可以看到,相同的条件语句switch在不同的方法中出现了重复,而且如果添加新的数据格式,那么需要改动的类过多。所以需要对我们的结构进行修改,以求更容易扩展和维护。

我们可以使用创造者子类分别生成对应的产品,这样添加新的数据格式时,只需要添加一个创造者子类即可,方便扩展和维护,这也就是比简单工厂模式更复杂一点的工厂模式,如下:

// 工厂模式
abstract class CommsManager {
    abstract function getHeaderText();
    abstract function getApptEncoder();
    abstract function getFooterText();
}

class BloggsCommsManager extends CommsManager {
    function getHeaderText()
    {
        return "BloggsCal Header\n";  
    }

    function getApptEncoder()
    {
        return new BloggsApptEncoder();
    }

    function getFooterText()
    {
        return "BloggsCal Footer\n";
    }
}

class MegaCommsManager extends CommsManager {
    function getHeaderText()
    {
        return "MegaCal Header\n";  
    }

    function getApptEncoder()
    {
        return new MegaApptEncoder();
    }

    function getFooterText()
    {
        return "MegaCal Footer\n";
    }
}

在这个时候,如果有新的数据格式,只需要添加一个创造类的子类即可,例如此时想获取MegaCal对应的解码器直接通过MegaCommsManager::getApptEncoder()获取即可。

完了么?我只能说,这个实例还没有完事。

新需求又来了,那就是不仅需要和A公司交流预约数据(Appointment),还需要交流待办事宜(Ttd)、联系人(Contact)等数据,同样的这些数据交流的格式也是BloggsCal和MegaCal,这个时候,我们可以直接在对应解码器的子类中添加处理事宜(TtD)和联系人(Contact)的方法,如下:

// 抽象工厂模式
abstract class CommsManager {
    abstract function getHeaderText();
    abstract function getApptEncoder();
    abstract function getTtdEncoder();
    abstract function getContactEncoder();
    abstract function getFooterText();
}

class BloggsCommsManager extends CommsManager {
    function getHeaderText()
    {
        return "BloggsCal Header\n";  
    }

    function getApptEncoder()
    {
        return new BloggsApptEncoder();
    }

    function getTtdEncoder()
    {
        return new BloggsTtdEncoder();
    }

    function getContactEncoder()
    {
        return new BloggsContactEncoder();
    }

    function getFooterText()
    {
        return "BloggsCal Footer\n";
    }
}

class MegaCommsManager extends CommsManager {
    function getHeaderText()
    {
        return "MegaCal Header\n";  
    }

    function getApptEncoder()
    {
        return new MegaApptEncoder();
    }

    function getTtdEncoder()
    {
        return new MegaTtdEncoder();
    }

    function getContactEncoder()
    {
        return new  MegaContactEncoder();
    }

    function getFooterText()
    {
        return "MegaCal Footer\n";
    }
}

//当然需要添加对应的TtdEncoder抽象类和ContactEncoder抽象类,以及他们的子类。

好啦,到这里就算是差不多结束了工厂模式的变形过程,我们可以来简单归纳下这个变形过程中的核心,如下:

1.将系统和实现的细节分离开,我们可在示例中移除或者添加任意数目的编码格式而不会影响系统。

2.对系统中功能相关的元素强制进行组合,因此,通过使用BloggsCommsManager,可以确定只使用与BloggsCal有关的类。

3.添加新产品时将会令人苦恼,因为不仅需要创建新产品的具体实现,而且为了支持它,我们必须修改抽象创建者和它的每个具体实现。

当然,我们可以创建一个标志参数来决定返回什么对象的单一的make()方法,而不用给每个工厂创建独立的方法,如下:

abstract class CommsManager {
    const APPT = 1;
    const TTD = 2;
    const CONTACT = 3;

    abstract function getHeaderText();
    abstract function make ( $flag_init  );
    abstract function getFooterText();
}

class BloggsCommsManager extends CommsManager {
    function getHeaderText()
   {
       return "BloggsCal Header\n";
   }

    function make( $flag_init )
   {
       switch ($flag_init) {
           case self::APPT:
               return new BloggsApptEncoder();
           case self::TTD:
               return new BloggsTtdEncoder();
           case self::CONTACT:
               return new BloggsContactEncoder();
       }
   }

    function getFooterText()
   {
       return "BloggsCal Header\n";
   }
}

此时,如果还需要添加交流其它的数据,此时只需在抽象创造者中添加一个新的flag_init标识,并在子创造者中的make方法中添加一个条件,相比来说比原先的更加容易扩展,只需修改少数地方即可。

最后,来简单总结下:

简单工厂:适用于生成数量少,功能简单的产品(BloggApptEncoder和MegaApptEncoder)

工厂模式:适用于生成数量多,功能复杂的产品(多个产品树[BloggCal,MegaCal]、单个产品族[apptEncoder]),相比简单工厂来说:业务更复杂,功能更多,但是产品族还是单个。

抽象工厂:适用于生成多个产品族、多个产品树的情景(产品族[appt,ttd,contact],产品树[Bloggcal,megaCal])。相比于工厂模式,更容易扩展添加新的产品族

好啦,本次记录就到这里了。

如果感觉不错的话,请多多点赞支持哦。。。

    原文作者:luyaran
    原文地址: https://blog.csdn.net/luyaran/article/details/82877315
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞