PHP八大设计模式|代码示例

2019年8月29日 0 条评论 242 次阅读 0 人点赞

一、单例模式

单例模式指的就是使某个类的对象仅允许被创建一次

应用场景: 一般创建一个对象需要消耗过多的资源,如:访问I0和数据库等资源或者有很多个地方都用到了这个实例。

具体代码实例:

<?php

class MyClass
{
    public $name;
    public $sex;
    public static $instance;

    private function __construct ()//构造函数私有化,防止外部new对象
    {
        $this->name = "我的名字叫小明";
    }

    public static function getInstanceCeshi ()
    {
        if (empty(self::$instance)) {
            self::$instance = new self();
            return self::$instance;
        } else {
            return self::$instance;
        }
    }
}

$myClass=MyClass::getInstanceCeshi();
echo $myClass->name;//输出:我的名字叫小明

这里我们需要注意几点:

  • 构造函数和析构函数必须声明为私有,防止外部程序new 类从而失去单例模式的意义
  • getInstance()方法必须设置为公有的,必须调用此方法 以返回实例的一个引用
  • ::操作符只能访问静态变量和静态函数
  • 使用单例模式生成一个对象后, 该对象可以被其它众多对象所使用

二:工厂模式

工厂模式是使用工厂类中的方法来生成某个对象的,而不是在代码中直接new该对象。使用工厂模式,可以避免当改变某个类的名字或者方法之后,在调用这个类的所有的代码中都修改它的名字或者参数。

模拟场景:我的一个项目中100个文件都使用到了第三方类库Test.php这个文件,因此在100个文件中我都需要new Test();但是某一天我将这个类的名字重命名后,改为Test1,那么我需要进入100个文件中把new Test();都改成new Test1();吗?
显然这样很不合理

解决方案代码示例说明

我拥有三个文件:(文件1:入口文件index.php)(文件2:工厂类文件:Factory.php)(文件3:第三方类库文件:Test.php)

以下是index.php代码示例:

<?php

spl_autoload_register('autoload');	

//注册autoload函数为自动调用函数

$test = Factory::createDatabase();	

//当使用Factory类中的静态方法时,由于该文件中没有Factory这个类,它会自动调用autoload方法,载入Factory这个类

//注:当然也可以选用命名空间的形式调用该静态方法,例如:\app\controller\Factory::createDatabase() 但是请注意的是,如果是使用了命名空间的形式调用,我们需要在autoload方法中修改相应的代码,让其找到该类,并且引入

$test->index();

/**
 * autoload方法监听当前文件,当出现文件中不存在的类名时,将自动载入该类
 * @param $class [当前文件不存在的类名]
 */
function autoload ($class)
{

    $dir = __DIR__;	//获取当前路径地址

    $requireFile = $dir . "/" . $class . ".php";	

    require $requireFile;	//引入当前文件中调用的不存在的类
}

以下是Factory.php代码示例

<?php

class Factory
{

    static function createDatabase ()
    {
        $test = new Test();	//由于该工厂类中也没有Test这个类,当这个方法在index.php中被调用时,Test类也会被监听,并自动载入
        return $test;
    }
}

以下是Test.php代码示例:

<?php

class Test
{
    public function index ()
    {
        echo "我已经成功被调用";
    }
}

此时,访问index.php这个文件时,我们就能调用Test.php这个类文件。

某一天我需要改变Test这个类的类名,只需要更改Test.php的文件名、Test类名以及Factory工厂类中new Test();中的这个Test名字即可。100个文件要使用Test这个类,仅仅只需要将工厂类引入,并且调用即可。但是前提是要注册监听自动载入函数autoload();

三、注册模式

注册模式,解决全局共享和交换对象。已经创建好的对象,挂在到某个全局可以使用的数组上,在需要使用的时候,直接从该数组上获取即可。将对象注册到全局的树上。任何地方直接去访问。

示例代码:

<?php

//注册模式类代码

class  Registry  {  

  protected  static  $store  =  array();     

  private static $instance;  

  private function __construct()
  {

  } 

  //用单例模式的方式实例化对象

  public static function instance() 
  {

      if(!isset(self::$instance)) {  

          self::$instance = new self();  

      }  

      return self::$instance;  

  }  

  public function  isValid($key)  {  

    return  array_key_exists($key,  Registry::$store);  

  }  

  public function  get($key)  {  

    if  (array_key_exists($key,  Registry::$store))  

    return  Registry::$store[$key];  

  }  

  public  function  set($key,  $obj)  {  

    Registry::$store[$key]  =  $obj;  

  } 

}  

//连接数据库类

class ConnectDB {  

    private $host;  

    private $username;  

    private $password;  

    private $conn;  


    public function __construct($host, $username, $password)
    {  

        $this->host = $host;  

        $this->username = $username;  

        $this->password = $password;  

    }  

    public function getConnect() {  

        return mysql_connect($this->host,$this->username,$this->password);  

    }  

}  

//使用方法

$reg = Registry::instance();  

$reg->set('db1', new ConnectDB('localhost', 'root', 'mckee'));  

$reg->set('db2', new ConnectDB('192.168.1.198', 'test', '0K5Dt@2jdc8#x@'));  

print_r($reg->get('db1'));  

print_r($reg->get('db2'));  

四:适配器模式

具体作用:将各种截然不同的函数接口封装成统一的API。

举例说明:PHP中的数据库操作有MySQL,MySQLi,PDO三种,可以用适配器模式统一成一致,使不同的数据库操作,统一成一样的API。类似的场景还有cache适配器,可以将memcache,redis,file,apc等不同的缓存函数,统一成一致。

具体实现方法如下:

首先定义一个接口(有几个方法,以及相应的参数)。然后,有几种不同的情况,就写几个类实现该接口。将完成相似功能的函数,统一成一致的方法。

代码示例:

<?php
 namespace app\interface;

 interface DatabaseApi
 {
     function connect($host, $user, $passwd, $dbname);

     function query($sql);

     function close();
 }

MySQL的API定制

<?php

namespace app\database;

use app\interface\DatabaseApi;

class MySQL implements DatabaseApi
{
    protected $conn;

    function connect($host, $user, $passwd, $dbname)
    {
        $conn = mysql_connect($host, $user, $passwd);

        mysql_select_db($dbname, $conn);

        $this->conn = $conn;
    }

    function query($sql)
    {
        $res = mysql_query($sql, $this->conn);

        return $res;
    }

    function close()
    {
        mysql_close($this->conn);
    }
}

Mysqli定制API

<?php
namespace app\database;

use app\interface\DatabaseApi;

class MySQLi implements DatabaseApi
{
    protected $conn;

    function connect($host, $user, $passwd, $dbname)
    {
        $conn = mysqli_connect($host, $user, $passwd, $dbname);

        $this->conn = $conn;
    }

    function query($sql)
    {
        return mysqli_query($this->conn, $sql);
    }

    function close()
    {
        mysqli_close($this->conn);
    }
}

PDO定制

<?php

namespace app\database;

use app\interface\DatabaseApi;

class PDO implements DatabaseApi
{
    protected $conn;

    function connect($host, $user, $passwd, $dbname)
    {
        $conn = new \PDO("mysql:host=$host;dbname=$dbname", $user, $passwd);

        $this->conn = $conn;
    }

	function query($sql)
    {
        return $this->conn->query($sql);
    }

    function close()
    {
        unset($this->conn);
    }
}

总结:通过以上案例,PHP与MySQL的数据库交互有三套API,在不同的场景下可能使用不同的API,那么开发好的代码,换一个环境,可能就要改变它的数据库API,那么就要改写所有的代码,使用适配器模式之后,就可以使用统一的API去屏蔽底层的API差异带来的环境改变之后需要改写代码的问题。

五、策略模式

策略模式,将一组特定的行为和算法封装成类,以适应某些特定的上下文环境。

举例说明:假如有一个电商网站系统,针对男性女性用户要各自跳转到不同的商品类目,并且所有的广告位展示不同的广告。在传统的代码中,都是在系统中加入各种if else的判断,硬编码的方式。如果有一天增加了一种用户,就需要改写代码。使用策略模式,如果新增加一种用户类型,只需要增加一种策略就可以。其他所有的地方只需要使用不同的策略就可以。

代码实现:

首先声明策略的接口文件,约定了策略的包含的行为。然后,定义各个具体的策略实现类

以下文件为:UserStrategy.php

<?php
/*
 * 声明策略文件的接口,约定策略包含的行为。
 */
interface UserStrategy
{
    function showAd();			//定义商品方法	

    function showCategory();	//定义展示类目方法
}

以下文件为 :FemaleUser.php

<?php

require_once 'Loader.php';

class FemaleUser implements UserStrategy
{
    function showAd()
    {
        echo "2016冬季女装";
    }

    function showCategory()
    {
        echo "女装";
    }
}

以下文件为 : MaleUser.php

<?php

require_once 'Loader.php';

class MaleUser implements UserStrategy
{
    function showAd()
    {
        echo "IPhone6s";
    }

    function showCategory()
    {
        echo "电子产品";
    }
}

以下文件为 :Page.php(策略分析文件)

<?php

require_once 'Loader.php';

class Page
{
    protected $strategy;

    function index()
    {
        echo "AD";

        $this->strategy->showAd();

        echo "<br>";

        echo "Category";

        $this->strategy->showCategory();

        echo "<br>";
    }

    function setStrategy(UserStrategy $strategy)
    {
        $this->strategy=$strategy;
    }
}

$page = new Page();

if(isset($_GET['male'])) {

//rquire(对应性别的类文件,也可以使用自动工厂模式中自动加载的方式)

    $strategy = new MaleUser();

}else {

//rquire(对应性别的类文件,也可以使用自动工厂模式中自动加载的方式)

    $strategy = new FemaleUser();

}

$page->setStrategy($strategy);

$page->index();

总结:通过以上方式,可以发现,在不同用户登录时显示不同的内容,并且解决了在显示时的硬编码的问题。如果要增加一种策略,只需要增加一种策略实现类,然后在入口文件中执行判断,传入这个类即可。实现了解耦。

六、观察者模式

用模式开发的优点是,能让我们的逻辑结构以及代码更加清晰,便于维护!

而我们为什么要用 “观察者模式”?这就需要从实际运用中来理解才能更好的运用!用如下的情境来说明吧。

事例:开始时我被安排做项目的登录,很快我就完成了。然后产品提出了另一个需求,用户登录后,给他们推送一条实时消息!然后我在登录成功的逻辑后加了一段代码,完成了登录后的实时消息推送。然而事情还没有完,产品又给加了个需求,需要给新登录的用户10块钱红包奖励,这个当然很简单,我又在消息推送后加了代码,完成了新登录用户的红包奖励(钱怎么到账的过程暂且不论),然而事情还没完没了了,产品不断的在加需求了,如非vip用户登录,给他推送10条需要注册VIP才能打开的信息,如根据客户习惯推送10条客户偏好的信息,如vip快到期的客户需要在客户登录后提醒要充值啦。。。。。。。等等,如是这般,那我就得不停的在登录后加代码,变得我开始看不懂哪个xxx写的代码了!

那么此时我们就得考虑用"观察者模式" 了

可以以这样的方式简单明了形容 观察者模式, 某个商场门口安排一个人进行观察,观察到有a类型的顾客进门,立即安排敲锣、打鼓、送鲜花,观察到有b类客户,立即安排购物袋,观察到c类客户,嗯嗯感觉他是来打酱油了,安排不要浪费表情了,什么欢迎仪式也没有。。。。。也就是说 观察者就是个‘势利眼’,看人下彩,根据观察给进来的顾客安排对应的某个服务或者某些服务!也许这个形容还不够恰当,但大体意思差不多了。

接下来就是重点了,描述了观察者模式的轮廓,那么就需要转化为代码来实际运用了!

一、首先得有两个接口类,用以框定观察者模式,一个被观察者接口类(一般申明有三个必须方法)

  • 添加观察者对象的方法
  • 删除观察者对象的方法
  • 通知观察者进行相应执行方法

一个观察者接口类(一般只有一个必须方法,就是执行)

如果直接甩代码可能有点难理解,那么就先给个示意图来明确一下吧!

二、根据观察者接口类的框定我们定下接口类如下代码:

<?php

// 被观察者接口(即用户接口,我们要根据用户的身份,添加相应的观察者,对其进行服务)

interface Subject{

    public function register(Observer $observer);  //添加(注册)观察者对象

    public function detach(Observer $observer);    //删除观察者对象

    public function notify();                      //通知观察者执行相应功能

}

// 观察者接口

interface Observer{

    public function watch();   //观察者要执行的方法

}

三、根据框定的结构,大概的理解一下就是,要实现 被观察者对象 存储各种观察者对象(完成各种功能的对象)存储起来,然后通只各观察者执行自己的功能,先看看如下的实现代码

<?php
// 被观察者继承类
class Action implements Subject{
 
     public $_observers=array(); //用于存储观察者对象
 
     //用于添加(注册)观察者对象
     public function register(Observer $observer){
         $this->_observers[]=$observer;
     }
 
     //用于删除观察者对象
     public function detach(Observer $observer){
 
        $index = array_search($observer, $this->_observers);
 
        if ($index === FALSE || ! array_key_exists($index, $this->_observers)) {
            return FALSE;
        }
 
        unset($this->_observers[$index]);
        return TRUE;
     }
 
     //通知各观察者
     public function notify(){
 
         //****重点,其实就是循环中执行各观察这对象的watch方法,不同功能方法内容不同但方法名相同
         foreach ($this->_observers as $observer) {
             $observer->watch();
         }
 
     }
 }
 
// 观察者1号继承类
class One implements Observer{
     public function watch(){

     	 echo "此人是重要用户,我应该主动服务";

         echo "<hr/>";
     }
 } 
 
//观察者2号继承类
 class Two implements Observer{
     public function watch(){

         echo "此人是vip用户,我应该敲锣打鼓,上去迎接";

         echo "<hr/>";
     }
 } 

//观察者3号继承类
 class Three implements Observer{
     public function watch(){

       echo "此人是潜在用户,我应该去上前去给他推销vip会员服务";

       echo "<hr/>";
     }
 }

 // 应用实例(此时来了个VIP用户,我们应该调用他相应的观察者服务)
$action=new Action();
$action->register(new Two());
$action->register(new One());
$action->notify();

//此时来了个普通潜在用户,我们应该调用他相应的推销服务观察者

$action=new Action();
$action->register(new Three());
$action->notify();

七、原型模式

1.有些时候,我们需要创建多个类似的大对象。如果直接通过new对象,开销很大,而且new完还得进行重复的初始化工作。可能把初始化工作封装起来的,但是对于系统来说,你封不封装,初始化工作还是要执行。

2.原型模式则不同,原型模式是先创建好一个原型对象,然后通过clone这个原型对象来创建新的对象,这样就免去了重复的初始化工作,系统仅需内存拷贝即可。

举例说明:如果说,我们现在正开发一个游戏,有不同的地图,地图大小都是一样的,并且都有海洋,但是不同的地图温度不一样。

<?php

//抽象原型类
Abstract class Prototype{

    abstract function __clone();

}

//具体原型类
class Map extends Prototype{

    public $width;

    public $height;

    public $sea;

    public function setAttribute(array $attributes)
    {

        foreach($attributes as $key => $val)
        {

            $this->$key = $val;

        }

    }

    public function __clone()
    {
    	echo "克隆时自动调用";
    }

}

//海洋类.这里就不具体实现了。
class Sea{}

//使用原型模式创建对象方法如下

//先创建一个原型对象

$map_prototype = new Map;

$attributes = array('width'=>40,'height'=>60,'sea'=>(new Sea));

$map_prototype->setAttribute($attributes);

//现在已经创建好原型对象了。如果我们要创建一个新的map对象只需要克隆一下

$new_map = clone $map_prototype;

var_dump($map_prototype);

var_dump($new_map);

八、装饰模式

为什么需要装饰器模式:

  • 我们要对一个已有的对象添加新功能,又不想修改它原来的结构。
  • 使用子类继承的方法去实现添加新功能,会不可避免地出现子类过多,继承链很长的情况。而且不少书籍都规劝我们竭力保持一个对象的父与子关系不超过3个。
  • 装饰器模式,可以提供对对象内容快速非侵入式地修改。

举例说明: 如果有一个游戏角色,他原来是默认没穿衣服的。现在游戏改进了,觉得这个角色,你可以自行搭配穿搭风格~

代码示例如下:

<?php

//声明一个装饰抽象类
abstract class Component
{
    //定义一个操作
    abstract public function operation ();
}

//声明一个装饰品抽象类继承装饰抽象类
abstract class Ornament extends Component
{
//声明一个受保护的变量用来挂载传入的实例
    protected $mountClass;

//调用setMountClass方法可以挂载对象,并赋给自身属性
    public function setMountClass ($mountClass)
    {
        $this->mountClass = $mountClass;
    }

//定义一个装饰行为,执行被挂载实例的operation()方法
    public function operation ()
    {
        if ($this->mountClass != null) {
            $this->mountClass->operation();
        }
    }
}

//开始声明我准备干吗
class MyOperation extends Component
{
    public function operation ()
    {
        echo '我准备穿衣服了' . PHP_EOL;
    }
}

//定义一个系上围巾的装饰操作
class OrnamentScarf extends Ornament
{
//重写父类的装饰行为,但是重点是必须执行一次父类装饰行为
    public function operation ()
    {
        parent::operation();//执行了父类的装饰行为

        echo '系上围巾' . PHP_EOL;
    }
}

//定义一个穿上裤子的装饰操作
class OrnamentTrousers extends Ornament
{

    public function operation ()
    {
        parent::operation();
        echo '穿上裤子 ' . PHP_EOL;
    }
}
//定义一个带上帽子的装饰操作
class OrnamentHat extends Ornament
{
    public function operation ()
    {
        parent::operation();//执行了父类的装饰行为
        echo '带上帽子 ' . PHP_EOL;
    }
}

//定义一个穿裙子的操作
/*
 * class OrnamentDress extends Ornament
 * {
 *
 *     // 具体方法就不实现了
 *
 * }
 *

 */

$MyOperation = new MyOperation();//实例化
$OrnamentScarf = new OrnamentScarf();
$OrnamentTrousers = new OrnamentTrousers();
$OrnamentHat = new OrnamentHat();

//装饰顺序是你的每一个装饰类实例化后的对象,以参数的形式传给下一个对象,如下所示

$OrnamentScarf->setMountClass($MyOperation);
$OrnamentTrousers->setMountClass($OrnamentScarf);
$OrnamentHat->setMountClass($OrnamentTrousers);
$OrnamentHat->operation();

//装饰顺序可以自行组合,也可以自行进行扩展,新的装饰类将不会影响到整体

兰陵美酒郁金香

左手代码右手诗

文章评论(0)