php 观察者模式
观察者模式,也称发布-订阅模式,定义了一个被观察者和多个观察者的、一对多的对象关系。
在被观察者状态发生变化的时候,它的所有观察者都会收到通知,并自动更新。
观察者模式通常用在实时事件处理系统、组件间解耦、数据库驱动的消息队列系统,同时也是MVC设计模式中的重要组成部分。
以下我们以订单创建为例。
当订单创建后,系统会发送邮件和短信,并保存日志记录。
1 问题
在没有用观察者模式的时候,如下:
class Order
{
// 订单状态
private $state = 0;
// 订单状态有变化时发送通知
public function addOrder()
{
$this->state = 1;
// 发送邮件
Email::update($this->state);
// 短信通知
Message::update($this->state);
// 记录日志
Log::update();
// 其他更多通知
}
}
代码中,在Order
类中调用各类的方法来实现通知。当在客户端创建订单:
$order = new Order();
$order->addOrder();
就会同时产生三个通知:发送邮件、发送短信和记录日志。
在系统小的时候,这是非常快捷有效的方式。
可是,当系统变大的时候,这种方法马上面临难以扩展的问题,并且容易出错:
- 如果订单不需要某种通知,比如不需要记录日志,则必须修改
Order
类,做状态的判断; - 如果再加一种通知方式,如系统消息通知,则除了增加新类,同时还需要修改
Order
类和客户端。
这两条都不符合面向对象中的开闭原则,会让系统越来越难维护。
2 解决
接下来我们用观察者模式解决这个问题。
2.1 被观察者
被观察者是一些具体的实例,比如订单管理、用户登陆、评论回复、状态审核等等。
别的功能会依赖于它们的状态进行各种动作。
/**
* 被观察者接口
*/
interface Observable
{
// 添加/注册观察者
public function attach(Observer $observer);
// 删除观察者
public function detach(Observer $observer);
// 触发通知
public function notify();
}
/**
* 被观察者
* 职责:添加观察者到$observers属性中,
* 有变动时通过notify()方法运行通知
*/
class Order implements Observable
{
// 保存观察者
private $observers = array();
// 订单状态
private $state = 0;
// 添加(注册)观察者
public function attach(Observer $observer)
{
$key = array_search($observer, $this->observers);
if ($key === false) {
$this->observers[] = $observer;
}
}
// 移除观察者
public function detach(Observer $observer)
{
$key = array_search($observer, $this->observers);
if ($key !== false) {
unset($this->observers[$key]);
}
}
// 遍历调用观察者的update()方法进行通知,不关心其具体实现方式
public function notify()
{
foreach ($this->observers as $observer) {
// 把本类对象传给观察者,以便观察者获取当前类对象的信息
$observer->update($this);
}
}
// 订单状态有变化时发送通知
public function addOrder()
{
$this->state = 1;
$this->notify();
}
// 获取提供给观察者的状态
public function getState()
{
return $this->state;
}
}
被观察者至少要实现attach()
、detach()
、notify()
三个方法,用以添加、删除和通知观察者。
通知的方式是,在类中的其他方法(如:创建订单)调用notify()
方法。
另外,观察者可能用到被观察者的一些状态信息。
所以,要在notify()
中把当前对象作为参数传给观察者,方便其通过提供的public
方法获得被观察者的状态信息。
本例用getState()
方法供给观察者获取状态信息。
2.2 观察者
观察者可能有多个,但每个观察者都必须实现Observer
接口规定的update()
方法,这是接收被观察者通知的唯一渠道。
/**
* 观察者接口
*/
interface Observer
{
// 接收到通知的处理方法
public function update(Observable $observable);
}
/**
* 观察者1:发送邮件
*/
class Email implements Observer
{
public function update(Observable $observable)
{
$state = $observable->getState();
if ($state) {
echo '发送邮件:您已经成功下单。';
} else {
echo '发送邮件:下单失败,请重试。';
}
}
}
/**
* 观察者2:短信通知
*/
class Message implements Observer
{
public function update(Observable $observable)
{
$state = $observable->getState();
if ($state) {
echo '短信通知:您已下单成功。';
} else {
echo '短信通知:下单失败,请重试。';
}
}
}
/**
* 观察者3:记录日志
*/
class Log implements Observer
{
public function update(Observable $observable)
{
echo '记录日志:生成了一个订单记录。';
}
}
这里有三个观察者:发送邮件、短信通知和记录日志,它们都实现了update()
方法。
其中,发送邮件和短信依赖于订单、也就是被观察者的状态,来决定发送消息的内容,记录日志则不需要订单的状态。
2.3 客户端
然后我们创建一个客户端,内容:
// 创建观察者对象
$email = new Email();
$message = new Message();
$log = new Log();
// 创建订单对象
$order = new Order();
// 向订单对象中注册3个观察者:发送邮件、短信通知、记录日志
$order->attach($email);
$order->attach($message);
$order->attach($log);
// 添加订单,添加时会自动发送通知给观察者
$order->addOrder();
echo '<br />';
// 删除记录日志观察者
$order->detach($log);
// 添加另一个订单,会再次发送通知给观察着
$order->addOrder();
执行应用后,会输出这样的消息:
发送邮件:您已经成功下单。添加日志:生成了一个订单记录。系统消息:您已下单成功。 发送邮件:您已经成功下单。添加日志:生成了一个订单记录。
对于不需要通知的观察者,用detach()
移出观察者列表即可。
这种情况就解开了类之间的耦合。
2.4 新增观察者
如果再需要新增一个观察者,如下,只需要添加观察者类本身,实现update()
方法。
/**
* 观察者4:系统消息
*/
class Alert implements Observer
{
public function update(Observable $observable)
{
echo '系统消息:您的订单有更新了~~~';
}
}
再到客户端中注册Alert
类到观察者列表中:
// 创建“系统消息”观察者
$alert = new Alert();
// 注册观察者到订单对象中
$order->attach($alert);
就能订阅被观察者的通知。
3 特点
在观察者模式中,被观察者完全不需要关心观察者,在自身状态有变化是,遍历执行观察者update()
方法即完成通知。
在观察者模式中,被观察者通过添加attach()
方法,提供给观察者注册,使自己变得可见。
当被观察者改变时,给注册的观察者发送通知。至于观察者如何处理通知,被观察者不需要关心。
这是一种良好的设计,对象之间不必相互理解,同样能够相互通信。
UML如下:
面向对象编程中,任何对象的状态都非常重要,它们是对象间交互的桥梁。
当一个对象的改变需要被其他对象关注时,观察者模式就派上用场了。
原文地址:https://www.cnblogs.com/-mrl/p/13282602.html
- 简单直白教你理解Java中四大引用强引用,软引用,弱引用,虚引用
- ubuntu系统上配置git
- Android源码编译出错No rule to make...
- Android中资源各种引用方式?,@,@*等等
- git撤销修改各种情况
- Android中ContentProvider简介
- 利用Androidstudio开发Java工程图文详解
- 小程序学习笔记分享(含1-tabBar、轮播图和九宫格)
- Android中ViewStub控件分析及使用
- 如何实现微信小程序的滚动加载功能
- 分享微信小程序推送消息步骤
- 实例分享微信小程序项目搭建(下)
- 实例分享微信小程序项目搭建(上)
- Android6.0源码分析之蓝牙显示接收到的文件
- php概述
- php教程
- php环境搭建
- PHP书写格式
- php变量
- php常量
- PHP注释
- php数组
- php字符串 string
- PHP整型 integer
- PHP浮点型 float
- php布尔型
- php数据类型之数组
- php数据类型之对象
- php数据类型之null
- php数据类型之间的转换
- php运算符
- php表达式
- PHP循环控制
- PHP流程控制
- php函数
- php全局变量
- PHP魔术变量
- php命名空间
- php 日期
- PHP包含文件
- php文件
- PHP 文件上传
- php Cookies
- php Sessions
- php email
- php安全email
- php错误处理
- PHP异常处理
- php过滤器
- PHP 高级过滤器
- php json
- php 表单
- PHP MySQL 简介
- PHP 连接 MySQL
- php创建数据库
- php 创建表
- php mysq 插入数据
- PHP MySQL 插入多条数据
- PHP MySQL 预处理语句
- php mysql 读取数据
- php mysql where
- PHP MySQL Order By
- PHP MySQL Update
- PHP MySQL Delete
- php ODBC
- 原理 + 代码|手把手教你用Python实现智能推荐算法
- 机器学习必刷题-基础概念篇(1):为什么用AUC做评价指标?
- 机器学习必刷题-手撕推导篇(3):FM与softmax
- Python面试必刷题系列(4)
- SQL面试必刷题(1) Case When
- 张量的数学运算
- 数据结构高频面试题-图
- nn.functional和nn.Module
- 20分钟学会DBSCAN聚类算法
- Selenium Firefox驱动程序:使用Firefox浏览器自动进行测试
- Dataset和DataLoader
- 3分钟短文 | PHP 删除目录下所有的文件,这3种写法哪个有bug?
- 【Hive】DDL 与 DML 操作
- 模型层
- 损失函数