Skip to content

PHP常用8大设计模式

PHP命名空间

  • 可以更好地组织代码,避免因为相同方法不同类在require之后冲突报错,与Java中的包类似。
1
2
3
4
5
<?php
namespace Test1;    //命名空间Test1
function test() {
    echo __FILE__;
}
1
2
3
4
5
<?php
namespace Test2;    //命名空间Test2
function test() {
    echo __FILE__;  //打印当前文件所在的绝对路径。
}
1
2
3
4
5
6
<?php
require 'Test1.php';
require 'Test2.php';
Test1\test();   //通过这种方式,使用命名空间下的方法或者类。Test1表示命名空间,test()表示该命名空间下的一个方法。
echo "<br>";
Test2\test();

类自动载入

  • __autoload的性质: 随着PHP项目的变大,会导致一个PHP文件的前面有很多的require去包含各种依赖的PHP文件。如果某个类删除,但是在别的文件里有导入的情况,就会导致致命错误。解决以上问题的方法,就是__autoload()函数。
1
2
3
4
5
6
<?php
class Test1 {
    static function test() {
        echo __FILE__;
    }
}
1
2
3
4
5
6
7
<?php
class Test2
{
    static function test() {
        echo __FILE__;
    }
}
1
2
3
4
5
6
7
8
9
<?php
Test1::test();
Test2::test();

function __autoload($class) {
    $dir  = __DIR__;
    $requireFile = $dir."\\".$class.".php";
    require $requireFile;
}
  • spl_autoload_register的性质: ↑↑↑代码,去动态的载入需要包含的文件。当使用某个类,而这个类没有包含到文件中时,就会调用__autoload()函数,去动态的加载这个文件。但是,当使用多个框架时,每个框架都会有自己的__autoload()实现,所以,会导致文件重复导入。故使用spl_autoload_register实现自动导入的函数,以字符串的形式传入该函数中,即可解决重复导入文件导致的错误问题。

    <?php
    spl_autoload_register('autoload1');
    spl_autoload_register('autoload2');
    //将实现自动导入的函数,以字符串的形式传入该函数中,即可解决重复导入文件导致的错误问题。
    Test1::test();
    Test2::test();
    
    function autoload1($class) {
        $dir  = __DIR__;
        $requireFile = $dir."\\".$class.".php";
        require $requireFile;
    }
    function autoload2($class) {
        $dir  = __DIR__;
        $requireFile = $dir."\\".$class.".php";
        require $requireFile;
    }
    

  • spl_autoload_register和__autoload区别:

    1. __autoload 只能有一个自动加载逻辑,而 spl_autoload_register 可以有多个,通过注册多个自动加载函数,PHP会按照注册的先后顺序依次调用,直到成功加载到类为止。因此它能避免你的应用程序和其它第三方库的自动加载逻辑产生冲突。
    2. __autoload 从PHP 7.2.0开始已被标记为deprecated(过时),后面会被废弃
    3. 若同时使用了 spl_autoload_register__autoload__autoload 会失效
    4. 优先使用 spl_autoload_register 来实现类的自动加载

PSR-0

  1. PHP的命名空间必须与绝对路径一致。
  2. 类名首字母大写。
  3. 除了入口文件之外,其他的PHP文件必须是一个类,不能有执行的代码。

工厂模式

  • 工厂模式主要是负责创建类的实例的(比如项目中需要一个缓存类,有些数据需要放在缓存里面,常用的缓存有memcache redis)
    <?php
    class Memcache {
        function set($k,$v) {
        }
        function get($k) {
        }
        function delete($k) {
        }
    }
    class Redis {
        function set($k,$v) {
        }
        function get($k) {
        }
        function delete($k) {
        }
    }
    class Cache {
        public static function factory() {
            return new Memcache();
            return new Redis();
        }
    }
    $cache = Cache::factory();
    $cache->set();
    $cache->get();
    $cache->delete();
    

单例模式

  • 单例模式能够确保一个类只有一个实例。(创建实例是会消耗内存的,PHP是解释性脚本语言,也就是说会等到所有的运行结束之后才会释放内存,这样在此中就会将该类实例化多次,单例模式可以优化这个问题。)
    <?php
    class Singletonmodel {
        private static $_instance = null;   
        private __construct() { }
        private __clone() { }
        public static function getInstance() {
            if(!(self::$_instance instanceof self)) {
                self::$_instance = new self();
            }
        }
    }
    Singletonmodel::getInstance();
    

注册树模式

  • 用来将一些对象注册到我们全局的树上面,它就可以让任何地方直接去访问,注册器模式一般只提供一个set(注册到全局的注册树上)与一个unset,get获取{相对于单例模式与工厂模式 不用每次都去调用}。
<?php
class Register {
    private static $object;
    static function set($alias, $object) {
        self::$object[$alias] = $object;
    }

    static function get($name) {
        return self::$object[$name];
    }

    function _unset($alias) {
        unset(self::$object[$alias]);
    }
}

// 将工厂模式与注册模式结合起来
// 场景:原本工厂模式获取到了单例模式连接数据库的实例对象之后,可以将数据库的对象映射到注册树上(映射时如下 起一个别名) 
class Factory {
    function static createdatabase() {
        $db = Database::getInstance();
        Register::set('别名db1',$db);
        return $db;
    }
}
// ↑↑↑↑↑↑↑↑↑ 这样的话工厂的构造只需要构造一次 其他任何地方想要再调用数据库对象的时候我们不需要调用

// 工厂方法也不需要调用单例模式获取实例 而是直接在注册器上(拿这个对象↓↓↓↓↓↓↓)
// anyone.php
$db = \namespace\Register::get('db1');

适配器模式

  • 适配器模式,可以将截然不同的函数接口封装成统一的API,实际应用的举例,PHP的数据库操作有mysql,mysqli,dpo 3种。可以使用适配器模式统一成一致的。类似的场景还有cache适配器,将memcache,redis,file,apc等不同的缓存函数,统一成一致的。
  • 所谓的适配器模式,就是将多种类同的数据操作方式,封装成不同的类,但是相同的格式,最后便捷的调用。
1
2
3
4
5
6
7
<?php
// 主要定义了 对于mysql mysqli pdo操作必要的三个接口
interface Databases {
    function connect($host,$user,$passwd,$dbname);
    function query($sql);
    function close()
}
<?php
// 主要定义实现了 Mysql的操作
class Mysql implements Databases {
    protected $result;
    function connect($host,$user,$userpwd,$dbname) {
        $result = mysql_connect($host,$user,$userpwd);
        mysql_select_db($dbname,$result);
        $this->result = $result;
    }

    function query($sql) {
        $res = mysql_query($sql,$this->result);
        return $res;
    }

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

}
1
2
3
4
5
6
7
8
9
<?php
// 主要定义了实现MySQLi的操作
mysqli_connect(host,user,pwd,dbname,port,scoket);
// socket   可选。规定 socket 或要使用的已命名 pipe。
class Mysqli implements Databses {
    function connect($host,$user,$userpwd,$dbname) {

    }
}
1
2
3
4
5
<?php
$db = new \namespace\nmspce\Mysql();
$db->connect(host,user,pwd,dbname);
$db->query('show databases');
$db->close();

策略模式

  • 策略模式,将一组特定的行为和算法封装成类,以适应某些特定的上下文环境,这种模式就是策略模式
  • 策略模式,写法主要是为了符合 设计模式的 开闭原则OCP (open closed principle): 类,函数,模块 对扩展开放, 对修改关闭.策略模式具体说就是 当需求变化时, 不要修改原有代码, 而是写新的扩展.
  • 实际应用举例,假如一个电商网站系统,针对男性女性用户要跳转到不同的商品类目,并且所有广告位展示不同广告(常规的做法是使用if else进行判断 这是一种硬编码的方式) 要灵活的应用(假如此时再加入一条条件就会出现不可估量的问题)。使用策略模式可以实现IOC、依赖倒置、控制反转
  • 代码思路
    1. 创建 interface
    2. 创建 男 相同方法的不同结果
    3. 创建 女 相同方法的不同结果
    4. 创建 首页
    5. 在首页 类外 调用 已完成接口实现的类
    6. 在首页 类外 实例化首页 判断 男与女 分别实例化 不同的类(男|女) 将实例结果放入受保护的属性中
  • 部分代码思路
    <?php
    class Test {
        protected $strategy;
        function index() {
            $result = $this->strategy;
            echo $result->AD();
            echo $result->INFO();
        }
        function setStrategy(\namespace\nmspc接口 $strategy) {
            $this->strategy = $strategy;
        }
    }
    $obj = new Test;
    if($_GET['sex']=='MALE') {
        $Strategy = \namespace\男
    } else {
        $Strategy = \namespace\女
    }
    $obj->setStrategy($Strategy);
    $obj->index();
    

观察者模式

  • 观察者模式,当一个对象状态发生改变时,依赖它的对象全部会收到通知,并自动更新
  • 一个事件发生后,要执行一连串更新操作。传统的编程方式,就是在事件的代码之后直接加入处理逻辑。当更新的逻辑增多之后,代码会变得难以维护。这种方式是耦合的,侵入式的,增加新的逻辑需要修改事件主题的代码
  • 观察者模式实现了低耦合,非侵入式的通知于更新机制
<?php
require_once 'Loader.php';
abstract class EventGenerator {
    private $observers = array();
    function addObserver(Observer $observer) {
        $this->observers[]=$observer;
    }
    function notify() {
        foreach ($this->observers as $observer) {
            $observer->update();
        }
    }
}
1
2
3
4
5
<?php
require_once 'Loader.php';
interface Observer {
    function update();//这里就是在事件发生后要执行的逻辑
}
<?php
//一个实现了EventGenerator抽象类的类,用于具体定义某个发生的事件
require 'Loader.php';
class Event extends EventGenerator {
    function triger() {
        echo "Event<br>";
    }
}
class Observer1 implements Observer {
    function update() {
        echo "逻辑1<br>";
    }
}
class Observer2 implements Observer {
    function update() {
        echo "逻辑2<br>";
    }
}
$event = new Event();
$event->addObserver(new Observer1());
$event->addObserver(new Observer2());
$event->triger();
$event->notify();

原型模式

  • 原型模式(对象克隆以避免创建对象时的消耗)
  • 与工厂模式类似,都是用来创建对象。
  • 与工厂模式的实现不同,原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象。这样就免去了类创建时重复的初始化操作。
  • 原型模式适用于大对象的创建,创建一个大对象需要很大的开销,如果每次new就会消耗很大,原型模式仅需要内存拷贝即可。
<?php
require_once 'Loader.php';
class Canvas{

    private $data;

    function init($width = 20, $height = 10)
    {
        $data = array();
        for($i = 0; $i < $height; $i++)
        {
            for($j = 0; $j < $width; $j++)
            {
                $data[$i][$j] = '*';
            }
        }
        $this->data = $data;
    }
    function rect($x1, $y1, $x2, $y2)
    {
        foreach($this->data as $k1 => $line)
        {
            if ($x1 > $k1 or $x2 < $k1) continue;
            foreach($line as $k2 => $char)
            {
                if ($y1>$k2 or $y2<$k2) continue;
                $this->data[$k1][$k2] = '#';
            }
        }
    }

    function draw(){
        foreach ($this->data as $line){
            foreach ($line as $char){
                echo $char;
            }
            echo "<br>;";
        }
    }
}
<?php
require 'Loader.php';
$c = new Canvas();
$c->init();
/ $canvas1 = new Canvas();
// $canvas1->init();
$canvas1 = clone $c;    //通过克隆,可以省去init()方法,这个方法循环两百次
//去产生一个数组。当项目中需要产生很多的这样的对象时,就会new很多的对象,那样
//是非常消耗性能的。
$canvas1->rect(2, 2, 8, 8);
$canvas1->draw();
echo "-----------------------------------------<br>";
// $canvas2 = new Canvas();
// $canvas2->init();
$canvas2 = clone $c;
$canvas2->rect(1, 4, 8, 8);
$canvas2->draw();

装饰器模式

  • 装饰器模式,可以动态的添加修改类的功能
  • 一个类提供了一项功能,如果要在修改并添加额外的功能,传统的编程模式,需要写一个子类继承它,并重写实现类的方法
  • 使用装饰器模式,仅需要在运行时添加一个装饰器对象即可实现,可以实现最大额灵活性。