本文简单介绍下,在 PHP 开发中涉及的常用的设计模式。包含:工厂模式、单例模式、注册器模式、适配器模式、策略模式、数据对象映射模式、观察者模式、原型模式、装饰器模式、迭代器模式、代理模式等。

1、工厂模式

使用一个工厂方法或类创建对象,而不是直接使用 new

/**
 * 工厂类
 */
class Factory
{

    /**
     * 制造类PHPHa的对象
     */
    public static function createObject()
    {
        return new PHPHa();
    }
}

// 【调用示例】
$object = Factory::createObject();

2、单例模式

使某个类的对象只允许被创建一次。

// 类
class Single
{

    /**
     * 唯一实例属性
     */
    protected static $instance;

    /**
     * 构造函数私有
     */
    private function __construct()
    {}

    /**
     * 析构函数私有
     */
    private function __destruct()
    {}

    /**
     * 克隆函数私有
     */
    private function __clone()
    {}

    /**
     * 创建唯一实例
     */
    public static function getInstance()
    {
        if (empty(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

// 【调用示例】
$instance = Single::getInstance();

3、注册器模式

用来解决全局共享和交换对象。

// 类
class Register
{

    /**
     * 对象数组
     */
    protected static $objects = [];

    /**
     * 设置对象
     */
    public static function set($name, $object)
    {
        self::$objects[$name] = $object;
    }

    /**
     * 获取对象
     */
    public static function get($name)
    {
        return self::$objects[$name];
    }

    /**
     * 删除对象
     */
    public static function unset($name)
    {
        unset(self::$objects[$name]);
    }
}

// 【调用示例】
Register::set('single', Single::getInstance());
$single = Register::get('single');
Register::unset('single');

4、适配器模式

可以将不同的函数接口封装成统一的 API

常见的场景,列如框架中的数据库驱动,对接有 mysql mysqli pdo ,可以用适配器模式对上层调用封装为统一的调用方式。同样还有各种缓存驱动文件,比如以 memcached redis file等作为缓存方式,是同样的道理。

/**
 * 数据库驱动接口
 */
interface IDB
{
    // 连接数据库
    public function connect($host, $user, $pwd, $db);
    // 查询数据库
    public function query($sql);
    // 关闭连接
    public function close();
}

/**
 * MySQL驱动
 */
class MySQL implements IDB
{

    protected $conn;

    public function connect($host, $user, $pwd, $db)
    {
        $this->conn = mysql_connect($house, $user, $pwd);
        mysql_select_db($db, $this->conn);
    }

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

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

/**
 * MySQLi驱动
 */
class MySQLi implements IDB
{

    protected $conn;

    public function connect($host, $user, $pwd, $db)
    {
        $this->conn = mysqli_connect($host, $user, $pwd, $db);
    }

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

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

/**
 * PDO驱动
 */
class PDO implements IDB
{

    protected $conn;

    public function connect($host, $user, $pwd, $db)
    {
        $this->conn = new \PDO("mysql:host={$host};dbname={$db}", $user, $pwd);
    }

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

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

// 【调用示例】
// MySQL
$conn = new MySQL('localhost', 'root', 'root', 'test');
$conn->query('SELECT * FROM news');
$conn->close();
// MySQLi
$conn = new MySQLi('localhost', 'root', 'root', 'test');
$conn->query('SELECT * FROM news');
$conn->close();
// PDO
$conn = new PDO('localhost', 'root', 'root', 'test');
$conn->query('SELECT * FROM news');
$conn->close();

5、策略模式

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

使用策略模式可以实现 IoC 控制反转,这是 Laravel 框架的核心模式。

例如:电商网站中的广告位,针对访客的性别可以展示不同的商品。

/**
 * 广告页面
 */
class Page
{

    /**
     * 策略
     */
    protected $strategy;

    /**
     * 广告位
     */
    public function adSpace()
    {
        $this->strategy->showAd();
    }

    /**
     * 设置策略
     */
    public function setStrategy(UserStrategy $strategy)
    {
        $this->strategy = $strategy;
    }
}

/**
 * 定义接口
 */
interface UserStrategy
{

    /**
     * 展示广告
     */
    public function showAd();
}

/**
 * 男性用户
 */
class MaleStrategy implements UserStrategy
{

    public function showAd()
    {
        echo '男性感兴趣的商品';
    }
}

/**
 * 女性用户
 */
class FemaleStrategy implements UserStrategy
{

    public function showAd()
    {
        echo '女性感兴趣的商品';
    }
}

// 【调用示例】
$page = new Page();
// 男性访客
isset($_GET['male']) && $page->setStrategy(new MaleStrategy());
// 女性访客
isset($_GET['female']) && $page->setStrategy(new FemaleStrategy());
// 展示广告
$page->adSpace();

6、数据对象映射模式

将对象和数据操作映射起来,对一个对象的操作会映射为对数据存储的操作。

在主流的 PHP 框架中,都包含有各自实现的 ORM 类。下面通过实现一个简单的 ORM 类来详细说明。

先创建一张用户表,如下:

CREATE TABLE `users`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '用户姓名',
  PRIMARY KEY (`id`)
);

通过数据对象映射模式对数据进行读取和更新:

/**
 * 用户类
 */
class User
{

    /**
     * 字段属性映射|用户ID
     */
    public $id;

    /**
     * 字段属性映射|用户姓名
     */
    public $name;

    /**
     * 数据库连接
     */
    protected $db;

    /**
     * 构造函数|属性赋值
     */
    public function __construct($id)
    {
        // 采用文章上面封装的数据库驱动
        $this->db = new MySQLi();
        $this->db->connect('localhost', 'root', 'root', 'test');
        $res = $this->db->query('SELECT * FROM users WHERE id = ', intval($id));
        $data = $res->fetch_assoc();
        // 赋值
        $this->id = intval($data['id']);
        $this->name = trim($data['name']);
    }

    /**
     * 析构函数|更新数据库
     */
    public function __destruct()
    {
        $this->db->query("UPDATE users SET name = '{$this->name}' WHERE id = {$this->id}");
    }
}

// 【调用示例】
$user = new User(1);
// 输出当前用户名
echo $user->name;
// 更新用户名
$user->name = '新用户名';

7、观察者模式

定义:当一个对象状态发生改变时,依赖它的对象会全部收到通知,并自动更新。

场景:一个事件发生后,要执行一连串的更新操作。传统的编程方式,就是在事件代码之后硬编码的方式加入处理逻辑。但是,当更新的逻辑逐渐变多后,就变得难以维护。这种方式是耦合的,侵入式的。

观察者模式实现了低耦合,非侵入式的通知和更新机制。

/**
 * 事件类
 */
class Event extends EventGenerator
{

    /**
     * 触发器
     */
    public function trigger()
    {
        // 事件发生后调用通知
        $this->notify();
    }
}

/**
 * 事件发生者
 */
abstract class EventGenerator
{

    /**
     * 观察者数组
     */
    private $observers = [];

    /**
     * 增加观察者
     */
    public function addObserver(Observer $observer)
    {
        $this->observers[] = $observer;
    }

    /**
     * 通知
     */
    public function notify()
    {
        // 依次通知更新
        foreach ($this->observers as $observer) {
            $observer->update();
        }
    }
}

/**
 * 观察者接口
 */
interface Observer
{

    /**
     * 更新操作
     */
    public function update($event_info = null);
}

/**
 * 观察者A
 */
class ObserverA implements Observer
{

    public function update($event_info = null)
    {
        echo '观察者A对应的逻辑处理' . PHP_EOL;
    }
}

/**
 * 观察者B
 */
class ObserverB implements Observer
{

    public function update($event_info = null)
    {
        echo '观察者B对应的逻辑处理' . PHP_EOL;
    }
}

// 【调用示例】
$event = new Event();
$event->addObserver(new ObserverA());
$event->addObserver(new ObserverB());
$event->trigger();

8、原型模式

与工厂模式类似,都是用来创建对象。

与工厂模式的实现不同,原型模式是先创建好一个原型对象,然后通过 clone 原型对象来创建新的对象。这样就免去了类创建时的初始化操作。

原型模式适用于大对象的创建。所谓大对象是指创建需要很大开销的对象,原型模式通过 clone 仅需内存拷贝即可。

/**
 * 原型类
 */
class Prototype
{

    /**
     * 初始化
     */
    public function init()
    {
        // 一些消耗资源的逻辑
    }

    /**
     * 具体功能
     */
    public function do()
    {
        echo '做一些事情';
    }
}

// 【调用示例】
$object = new Prototype();
$object->init();
$object1 = clone $object;
$object1->do();
$object2 = clone $object;
$object2->do();

9、装饰器模式

可以动态的添加修改类的功能。

一个类提供了一项功能,如果要添加额外的功能,传统的编程模式是通过创建一个子类继承这个类,并重新实现父类的方法。

使用装饰器模式,仅需在运行时添加一个装饰器对象即可,可以实现最大的灵活性。

/**
 *  装饰器接口
 */
interface Decorator
{

    /**
     * 前置操作
     */
    public function before();

    /**
     * 后置操作
     */
    public function after();
}

/**
 * 装饰器A
 */
class DecoratorA implements Decorator
{

    public function before()
    {
        echo '装饰器A前置操作' . PHP_EOL;
    }

    public function after()
    {
        echo '装饰器A后置操作' . PHP_EOL;
    }
}

/**
 * 装饰器B
 */
class DecoratorB implements Decorator
{

    public function before()
    {
        echo '装饰器B前置操作' . PHP_EOL;
    }

    public function after()
    {
        echo '装饰器B后置操作' . PHP_EOL;
    }
}

/**
 * 演示类
 */
class Test
{

    /**
     * 装饰器数组
     */
    protected $decorators = [];

    /**
     * 挂载装饰器
     */
    public function addDecorator(Decorator $decorator)
    {
        $this->decorators[] = $decorator;
    }

    /**
     * 依次调用前置操作
     */
    private function doBefore()
    {
        // 正序
        foreach ($this->decorators as $decorator) {
            $decorator->before();
        }
    }

    /**
     * 依次调用后置操作
     */
    private function doAfter()
    {
        // 倒序|反转数组
        $decorators = array_reverse($this->decorators);
        foreach ($decorators as $decorator) {
            $decorator->after();
        }
    }

    /**
     * 演示类主逻辑
     */
    public function do()
    {
        $this->doBefore();
        echo '演示类主逻辑' . PHP_EOL;
        $this->doAfter();
    }
}

// 【调用示例】
$test = new Test();
$test->addDecorator(new DecoratorA());
$test->addDecorator(new DecoratorB());
$test->do();

10、迭代器模式

在不需要了解内部实现的前提下,遍历一个聚合对象的内部元素。

相比于传统编程模式,迭代器模式可以隐藏遍历元素所需的操作。

PHP 中迭代器的实现需要继承 PHP 标准库中的 Iterator interface,并实现其对应的 5 个接口方法。

/**
 * 查询数据库中的用户信息
 */
class Users implements \Iterator
{

    /**
     * 当前索引
     */
    protected $index;

    /**
     * 数据集合
     */
    protected $data;

    /**
     * 构造函数
     */
    public function __construct()
    {
        // 链接数据库|仅做演示
        // 此处代码应用工厂模式拆分
        $db = new MySQLi('localhost', 'root', 'root', 'test');
        $query = $db->query('SELECT * FROM users');
        $this->data = $query->fetch_all(MYSQLI_ASSOC);
    }

    /**
     * 当前元素
     */
    public function current()
    {
        return $this->data[$this->index];
    }

    /**
     * 下一个元素
     */
    public function next()
    {
        $this->index ++;
    }

    /**
     * 验证是否还有下一个元素
     */
    public function valid()
    {
        return $this->index < count($this->data);
    }

    /**
     * 重置索引
     */
    public function rewind()
    {
        $this->index = 0;
    }

    /**
     * 索引位置
     */
    public function key()
    {
        return $this->index;
    }
}

// 【调用示例】
$users = new Users();
foreach ($users as $user) {
    print_r($user);
}

11、代理模式

在客户端与实体之间建立一个代理对象 Proxy,客户端对实体进行的操作全部委派给代理对象,隐藏具体的实现细节。

Proxy 还可以与业务代码分离,部署到另外的服务器,业务代码中通过 RPC 来委派任务。

/**
 * 代理接口
 */
interface IProxy
{

    public function doA();

    public function doB();
}

/**
 * 代理类
 */
class Proxy implements IProxy
{

    public function doA()
    {
        echo '一些操作A';
    }

    public function doB()
    {
        echo '一些操作B';
    }
}

// 【调用示例】
$proxy = new Proxy();
// 通过代理进行操作
$proxy->doA();
$proxy->doB();

标签:PHP 设计模式