音效素材网提供各类素材,打造精品素材网站!

站内导航 站长工具 投稿中心 手机访问

音效素材

PHP设计模式之工厂模式(Factory)入门与应用详解
日期:2021-09-06 20:52:34   来源:脚本之家

本文实例讲述了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])。相比于工厂模式,更容易扩展添加新的产品族

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

希望本文所述对大家PHP程序设计有所帮助。

    您感兴趣的教程

    在docker中安装mysql详解

    本篇文章主要介绍了在docker中安装mysql详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编...

    详解 安装 docker mysql

    win10中文输入法仅在桌面显示怎么办?

    win10中文输入法仅在桌面显示怎么办?

    win10系统使用搜狗,QQ输入法只有在显示桌面的时候才出来,在使用其他程序输入框里面却只能输入字母数字,win10中...

    win10 中文输入法

    一分钟掌握linux系统目录结构

    这篇文章主要介绍了linux系统目录结构,通过结构图和多张表格了解linux系统目录结构,感兴趣的小伙伴们可以参考一...

    结构 目录 系统 linux

    PHP程序员玩转Linux系列 Linux和Windows安装

    这篇文章主要为大家详细介绍了PHP程序员玩转Linux系列文章,Linux和Windows安装nginx教程,具有一定的参考价值,感兴趣...

    玩转 程序员 安装 系列 PHP

    win10怎么安装杜比音效Doby V4.1 win10安装杜

    第四代杜比®家庭影院®技术包含了一整套协同工作的技术,让PC 发出清晰的环绕声同时第四代杜比家庭影院技术...

    win10杜比音效

    纯CSS实现iOS风格打开关闭选择框功能

    这篇文章主要介绍了纯CSS实现iOS风格打开关闭选择框,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作...

    css ios c

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的办法

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的

    Win7给电脑C盘扩容的办法大家知道吗?当系统分区C盘空间不足时,就需要给它扩容了,如果不管,C盘没有足够的空间...

    Win7 C盘 扩容

    百度推广竞品词的投放策略

    SEM是基于关键词搜索的营销活动。作为推广人员,我们所做的工作,就是打理成千上万的关键词,关注它们的质量度...

    百度推广 竞品词

    Visual Studio Code(vscode) git的使用教程

    这篇文章主要介绍了详解Visual Studio Code(vscode) git的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...

    教程 Studio Visual Code git

    七牛云储存创始人分享七牛的创立故事与

    这篇文章主要介绍了七牛云储存创始人分享七牛的创立故事与对Go语言的应用,七牛选用Go语言这门新兴的编程语言进行...

    七牛 Go语言

    Win10预览版Mobile 10547即将发布 9月19日上午

    微软副总裁Gabriel Aul的Twitter透露了 Win10 Mobile预览版10536即将发布,他表示该版本已进入内部慢速版阶段,发布时间目...

    Win10 预览版

    HTML标签meta总结,HTML5 head meta 属性整理

    移动前端开发中添加一些webkit专属的HTML5头部标签,帮助浏览器更好解析HTML代码,更好地将移动web前端页面表现出来...

    移动端html5模拟长按事件的实现方法

    这篇文章主要介绍了移动端html5模拟长按事件的实现方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家...

    移动端 html5 长按

    HTML常用meta大全(推荐)

    这篇文章主要介绍了HTML常用meta大全(推荐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参...

    cdr怎么把图片转换成位图? cdr图片转换为位图的教程

    cdr怎么把图片转换成位图? cdr图片转换为

    cdr怎么把图片转换成位图?cdr中插入的图片想要转换成位图,该怎么转换呢?下面我们就来看看cdr图片转换为位图的...

    cdr 图片 位图

    win10系统怎么录屏?win10系统自带录屏详细教程

    win10系统怎么录屏?win10系统自带录屏详细

    当我们是使用win10系统的时候,想要录制电脑上的画面,这时候有人会想到下个第三方软件,其实可以用电脑上的自带...

    win10 系统自带录屏 详细教程

    + 更多教程 +
    ASP编程JSP编程PHP编程.NET编程python编程