PHP常用的三种设计模式

Posted 李留广

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP常用的三种设计模式相关的知识,希望对你有一定的参考价值。

本文为大家介绍常用的三种php设计模式:单例模式、工厂模式、观察者模式,有需要的朋友可以参考下。

一、首先来看,单例模式

所谓单例模式,就是确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例,即在应用程序中只会有这个类的一个实例存在。
通常单例模式用在仅允许数据库访问对象的实例中,从而防止打开多个数据库连接,单例模式是一种常见的设计模式,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、数据库操作、显卡的驱动程序常被设计成单例。

一个单例类应包括以下几点:
和普通类不同,单例类不能被直接实例化,只能是由自身实例化。因此,要获得这样的限制效果,构造函数必须标记为private。
要让单例类不被直接实例化而能起到作用,就必须为其提供这样的一个实例。因此,就必须要让单例类拥有一个能保存类的实例的私有静态成员变量和对应的一个能访问到实例的公共静态方法。
在PHP中,为防止对单例类对象的克隆来打破单例类的上述实现形式,通常还为基提供一个空的私有__clone()方法。好吧,废话不多说,概括如下

单例模式有以下3个特点:

1.只能有一个实例,必须拥有一个构造函数,并且必须被标记为private

2.必须自行创建这个实例,拥有一个保存类的实例的静态成员变量

3.必须给其他对象提供这一实例,拥有一个访问这个实例的公共的静态方法

单例类不能再其它类中直接实例化,只能被其自身实例化。它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用

那么为什么要使用PHP单例模式?

PHP一个主要应用场合就是应用程序与数据库打交道的场景,在一个应用中会存在大量的数据库操作,针对数据库句柄连接数据库的行为,使用单例模式可以避免大量的new操作。因为每一次new操作都会消耗系统和内存的资源。

在以往的项目开发中,没使用单例模式前的情况如下:

技术分享图片
//初始化一个数据库句柄
$db = new DB(...);
//比如有个应用场景是添加一条评论信息
$db->addComment();
......
//如果我们要在另一地方使用这个评论信息,这时要用到数据库句柄资源,可能会这么做
......
function comment() {
   $db = new DB(...);
   $db->getCommentInfo();
......
//可能有些朋友也许会说,可以直接使用global关键字!
   global $db;
......
技术分享图片

的确global可以解决问题,也起到单例模式的作用,但在OOP中,我们建议拒绝这种编码。因为global存在安全隐患(全局变量不受保护的本质)。

全局变量是面向对象程序员遇到的引发BUG的主要原因之一。这是因为全局变量将类捆绑于特定的环境,破坏了封装。如果新的应用程序无法保证一开始就定义了相同的全局变量,那么一个依赖于全局变量的类就无法从一个应用程序中提取出来并应用到新应用程序中。

确切的讲,单例模式恰恰是对全局变量的一种改进,避免那些存储唯一实例的全局变量污染命名空间。你无法用错误类型的数据覆写一个单例。这种保护在不支持命名空间的PHP版本里尤其重要。因为在PHP中命名冲突会在编译时被捕获,并使脚本停止运行。

我们用单例模式改进下示例:

技术分享图片
class Single {
    private $name;//声明一个私有的实例变量
    private function __construct(){//声明私有构造方法为了防止外部代码使用new来创建对象。
    
    }

    static public $instance;//声明一个静态变量(保存在类中唯一的一个实例)
    static public function getinstance(){//声明一个getinstance()静态方法,用于检测是否有实例对象
        if(!self::$instance) self::$instance = new self();
        return self::$instance;
    }

    public function setname($n){ $this->name = $n; }
    public function getname(){ return $this->name; }
}

$oa = Single::getinstance();
$ob = Single::getinstance();
$oa->setname(‘hello php world‘);
$ob->setname(‘good morning php‘);
echo $oa->getname();//good morning php
echo $ob->getname();//good morning php
技术分享图片

单例模式的优缺点:

优点:

1. 改进系统的设计

2. 是对全局变量的一种改进

缺点:

1. 难于调试

2. 隐藏的依赖关系

3. 无法用错误类型的数据覆写一个单例

二、工厂模式

 工厂模式就是一种类,是指包含一个专门用来创建其他对象的方法的类,工厂类在多态性编程实践中是至关重要的,它允许动态的替换类,修改配置,通常会使应用程序更加灵活,熟练掌握工厂模式高级PHP开发人员是很重要的。

 工厂模式通常用来返回符合类似接口的不同的类,工厂的一种常见用法就是创建多态的提供者,从而允许我们基于应用程序逻辑或者配置设置来决定应实例化哪一个类,例如,可以使用这样的提供者来扩展一个类,而不需要重构应用程序的其他部分,从而使用新的扩展后的名称 。

通常,工厂模式有一个关键的构造,根据一般原则命名为Factory的静态方法,然而这只是一种原则,工厂方法可以任意命名,这个静态还可以接受任意数据的参数,必须返回一个对象。

具有为您创建对象的某些方法,这样就可以使用工厂类创建对象,工厂模式在于可以根据输入参数或者应用程序配置的不同来创建一种专门用来实现化并返回其它类的实例的类,而不直接使用new,这样如果想更改创建的对象类型,只需更改该工厂即可,

先举个示例吧:

技术分享图片
<?php
class Factory {//创建一个基本的工厂类
    static public function fac($id){//创建一个返回对象实例的静态方法
        if(1 == $id) return new A();
        elseif(2==$id) return new B();
        elseif(3==$id) return new C();
        return new D();
    }
}

interface FetchName {//创建一个接口
    public function getname();//
}

class A implements FetchName{
    private $name = "AAAAA";
    public function getname(){ return $this->name; }
}

class C implements FetchName{
    private $name = "CCCCC";
    public function getname(){ return $this->name; }
}
class B implements FetchName{
    private $name = "BBBBB";
    public function getname(){ return $this->name; }
}

class D implements FetchName{
    private $name = "DDDDD";
    public function getname(){ return $this->name; }
}


$o = Factory::fac(6);//调用工厂类中的方法
if($o instanceof FetchName){
  echo  $o->getname();//DDDDD
}

$p=Factory::fac(3);
echo $p->getname();//CCCCC

?>
技术分享图片

个人意见,再说简单点吧,PHP工厂模式就是用一个工厂方法来替换掉直接new对象的操作,就是为方便扩展,方便使用,在新增实现基类中的类中方法时候,那么在工厂类中无需修改,传入参数可以直接使用,具体就是跳过工厂类修改,直接使用工厂类输出想要的结果。在传统习惯中,如果要生成一个类的话,在代码中直接new一个对象,比如:

class Database{
    
}
 
$db = new Database();

下面介绍工厂模式的操作方法:

技术分享图片
class Database{  
 
}
 
//创建一个工厂类
class Factory
{
   //创建一个静态方法
   static function createDatabase(){
       $db = new Database;
       return $db;
   }
}
技术分享图片

那么,当我们想创建一个数据库类的话,就可以使用这样的方法:

<?php 
    $db = Factory::createDatabase();
?>

简单工厂模式比直接new一个对象的好处是,比如Database这个类在很多php文件中都有使用到,当Database这个类发生了某些变更,比如修改了类名、或者一些参数发生了变化,那这时候如果你使用的是$db = new Database这种传统方法生成对象,那么在所有包含这种生成对象的php文件代码中都要进行修改。而使用工厂模式,只要在工厂方法或类里面进行修改即可。而且工厂模式是其他设计模式的基础。
对上面的简单工厂模式再进一步优化,比如:

 利用工厂类生产对象:

技术分享图片
<?php
class Example
{
    // The parameterized factory method
    public static function factory($type)
    {
        if (include_once ‘Drivers/‘ . $type . ‘.php‘) {
            $classname = ‘Driver_‘ . $type;
            return new $classname;
        } else {
            throw new Exception(‘Driver not found‘);
        }
    }
}
 
// Load a mysql Driver
$mysql = Example::factory(‘MySQL‘);
 
// Load an SQLite Driver
$sqlite = Example::factory(‘SQLite‘);
?>
技术分享图片

简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
要理解工厂模式这个概念,让我们最好谈一下许多开发人员从事大型系统的艰苦历程。在更改一个代码片段时,就会发生问题,系统其他部分 —— 您曾认为完全不相关的部分中也有可能出现级联破坏。
该问题在于紧密耦合 。系统某个部分中的函数和类严重依赖于系统的其他部分中函数和类的行为和结构。您需要一组模式,使这些类能够相互通信,但不希望将它们紧密绑定在一起,以避免出现联锁。
在大型系统中,许多代码依赖于少数几个关键类。需要更改这些类时,可能会出现困难。例如,假设您有一个从文件读取的 User 类。您希望将其更改为从数据库读取的其他类,但是,所有的代码都引用从文件读取的原始类。这时候,使用工厂模式会很方便。

看下实例:

技术分享图片
<?php
 
    interface IUser
    {
      function getName();
    }
 
    class User implements IUser
    {        
      public $id;
      public function __construct( $id ) { }
 
      public function getName()
      {
        return "Fantasy";
      }
    }
?>
技术分享图片

传统方法使用 User 类,一般都是这样:

技术分享图片
<?php
//在页面1
$obj = new User(1);
 
//在页面2
$obj2 = new User(2);
 
//在页面3
$obj3 = new User(3);
....

?>
技术分享图片

这时候,由于新的需求,使得User类要新增个参数或者User类名称发生变化,User 类代码发生变动,即:

技术分享图片
<?php
class User implements IUser
{
  public $id,$pre;
  public function __construct( $id , $pre = ‘‘) {...}
 
  public function getName()
  {
    return $this->pre."Fantasy";
  }
}

?>
技术分享图片

接着,恐怖的事情发生了,假设之前有 100 个页面引用了之前的 User 类,那么这 100 个页面都要发生相应的改动:

技术分享图片
//在页面1
$obj = new User(1,‘aaa‘);
 
//在页面2
$obj = new User(2,‘aaa‘);
 
//在页面3
$obj = new User(3,‘aaa‘);
...
技术分享图片

本来是一个小小的改动,但因紧密耦合的原因使得改动大吐血。而使用工厂模式则可以避免发生这种情况:

技术分享图片
//User类为变动前
class UserFactory
{
  public static function Create( $id )
  {
    return new User( $id );
  }
}
 
//页面1
$uo1 = UserFactory::Create( 1 );
 
//页面2
$uo12 = UserFactory::Create( 2 );
....
技术分享图片

这时候需求变动,User 类也发生变动:

技术分享图片
<?php
class User implements IUser
{
  public $id,$pre;
  public function __construct( $id , $pre = ‘‘) {...}
 
  public function getName()
  {
    return $this->pre."Jack";
  }
}
?>
技术分享图片

但是,我们不再需要去改动这 100 个页面,我们要改的仅仅是这个工厂类:

技术分享图片
//
class UserFactory
{
  public static function Create( $id,$pre = ‘aaa‘ )
  {
    return new User( $id ,$pre);
  }
}
技术分享图片

其他100个页面不用做任何改动,这就是工厂设计模式带来的好处。看下UML图:

技术分享图片

三、观察者模式
观察者模式为您提供了避免组件之间紧密耦合的另一种方法。该模式非常简单:观察者模式是一种事件系统,意味着这一模式允许某个类观察另一个类的状态,当被观察的类状态发生改变的时候,观察类可以收到通知并且做出相应的动作;

现在有两派,有的人建议使用设计模式,有的人不建议使用设计模式!
这就好比写文章一样,有的人喜欢文章按照套路走,比如叙事性质的文章,时间,地点,人物,事件。而有的人喜欢写杂文或者散文,有的人喜欢写诗词!
现在写代码很多地方类似于写文章,但是在有些地方比写文章需要更多的技能!写文章写多了一般也能写出优秀的文章,而代码也一样,写多了也能写出很多有写的代码!
很多时候,我看设计模式的时候,有些设计模式只是吻合我的代码习惯。但是你硬去套它,那么反而适得其反。——很多时候是学会了招式,在应用中不知不觉的使用上这些招式,才能掌握其道,但是也不要拘泥于招式,正所谓“无招胜有招”吗?
我学设计模式的初衷,就是知道有这么个玩意儿?脑子里有这么个印象,也不会生套它!如果设计模式不符合你的习惯对你阅读代码反而是不利的!
观察者模式定义对象的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新!
设计原则 

在观察者模式中,会改变的是主题的状态以及观察者的数目。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题。——找出程序中会变化的方面,然后将其和固定不变的方面相分离!
 
主题和观察者都使用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点! ——针对接口编程,不针对实现编程!
 
观察者模式利用“组合”将许多观察者组合进主题中。对象(观察者——主题)之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式产生的。 ——多用组合,少用继承!
好了,不说太多废话,直接上代码:
技术分享图片
<?php
/**
 * 观察者模式
 * @author: Fantasy
 * @date: 2017/02/17
 */
 
class Paper{ /* 主题    */
    private $_observers = array();
 
    public function register($sub){ /*  注册观察者 */
        $this->_observers[] = $sub;
    }
     
    public function trigger(){  /*  外部统一访问    */
        if(!empty($this->_observers)){
            foreach($this->_observers as $observer){
                $observer->update();
            }
        }
    }
}
 
/**
 * 观察者要实现的接口
 */
interface Observerable{
    public function update();
}
 
class Subscriber implements Observerable{
    public function update(){
        echo "Callback\n";
    }
}
?>
技术分享图片

下面是测试代码:

技术分享图片
/*  测试    */
$paper = new Paper();
$paper->register(new Subscriber());
//$paper->register(new Subscriber1());
//$paper->register(new Subscriber2());
$paper->trigger();
技术分享图片
总结
       当新对象要填入的时候,只需要在主题(又叫可观察者)中进行注册(注册方式很多,你也可以在构造的时候,或者框架访问的接口中进行注册),然后实现代码直接在新对象的接口中进行。这降低了主题对象和观察者对象的耦合度。
好的设计模式不会直接进入你的代码中,而是进入你的大脑中。















以上是关于PHP常用的三种设计模式的主要内容,如果未能解决你的问题,请参考以下文章

php文件之间传值的三种主流并且常用的方式

PHP合并数组的三种方法的分析与比较

php-fpm解读-进程管理的三种模式

linux vim三种模式如何切换

PHP pdo 错误的三种模式

jenkins构建任务常用的三种模式