本文简单介绍下,在 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();