观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题发生变化时,通知所有的观察对象。 总的来说, 观察者是解除耦合的重要手段。
比如在一个登陆系统中,产品今天说在登陆时要加日志,明天又说在登陆时要发邮件或是发短信,后天又说要记录另外的一些东西。代码一直在打补丁,都改乱了,这时观察者模式可能会很好的解决这个问题,当登陆发生时去通知所有的观察者。
我们可以设计一下第一版的观察者模式。
LoginSubjectInterface 文件
<?php
/**
* Created by PhpStorm.
* User: v_bivwei
* Date: 2019/9/23
* Time: 17:37
*/
interface LoginSubjectInterface
{
}
LoginObserverInterface 文件
<?php
/**
* Created by PhpStorm.
* User: v_bivwei
* Date: 2019/9/23
* Time: 17:38
*/
interface LoginObserverInterface
{
}
LoginSms 短信通知类
<?php
/**
* Created by PhpStorm.
* User: v_bivwei
* Date: 2019/9/23
* Time: 17:40
*/
class LoginSms implements LoginObserverInterface
{
private $loginSubject;
public function __construct(LoginSubjectInterface $loginSubject)
{
$this->loginSubject = $loginSubject;
$this->loginSubject->attach($this);
}
public function doNotify()
{
echo 'sms notify' . PHP_EOL;
}
}
LoginEmailNotify 邮件通知类
<?php
/**
* Created by PhpStorm.
* User: v_bivwei
* Date: 2019/9/23
* Time: 17:38
*/
require_once 'LoginObserverInterface.php';
class LoginEmailNotify implements LoginObserverInterface
{
private $loginSubject;
public function __construct(LoginSubjectInterface $loginSubject)
{
$this->loginSubject = $loginSubject;
$this->loginSubject->attach($this);
}
public function doNotify()
{
echo 'send email notify' . PHP_EOL;
}
}
Login 具体的登陆类
<?php
/**
* Created by PhpStorm.
* User: v_bivwei
* Date: 2019/9/23
* Time: 17:35
*/
require_once 'LoginSubjectInterface.php';
class Login implements LoginSubjectInterface
{
private $observers;
public function __construct()
{
$this->observers = [];
}
/**
* 加入观察者
*
* @param LoginObserverInterface $loginSubject
*/
public function attach(LoginObserverInterface $loginObserver)
{
$this->observers[] = $loginObserver;
}
/**
* 移除观察者
*
* @param LoginObserverInterface $loginObserver
*/
public function detach(LoginObserverInterface $loginObserver)
{
//todo
}
/**
* 通知观察者事件
*/
public function notify()
{
foreach ($this->observers as $observer) {
$observer->doNotify();
}
}
/**
* 处理登陆
*
* @return array
*/
public function handleLogin($param)
{
$isLogin = false;
//执行登陆
switch ($this->doLogin($param)) {
case 0:
$message = '登陆成功';
$isLogin = true;
break;
case 1:
$message = '帐号或密码不对';
break;
case 2:
$message = '账号已经被禁用';
break;
default:
$message = '登陆失败';
}
$this->notify();
return [
'isLogin' => $isLogin,
'message' => $message,
];
}
/**
* 执行具体登陆操作
*
* @return int
*/
public function doLogin($param)
{
//todo
return rand(0, 2);
}
}
index 入口文件
<?php
/**
* Created by PhpStorm.
* User: v_bivwei
* Date: 2019/9/23
* Time: 17:42
*/
require_once 'Login.php';
require_once 'LoginEmailNotify.php';
require_once 'LoginSms.php';
$loginObject = new Login();
new LoginEmailNotify($loginObject);
new LoginSms($loginObject);
$result = $loginObject->handleLogin(['email' => '1234567890@qq.com', 'passwd' => '123456']);
echo json_encode($result, 285);
运行结果

这里有几点可以优化
- 我们发现了在观察者类中有一部分重复代码,每个观察者类中,就是向被观察者业务类执行 attch 操作,这部分可以抽出基类作为封装。另外一点没实现的就是 doNotify 必须作为一个参数将当前登陆的帐号或手机号传递过去, 作为发送短信依据。
- 我们大部分使用观察者都是使用推的模式, 被动接口 notify,其实还有一种模式为 拉 模式,其实核心就是在 notify 中返向调用符合自身业务的接口去处理自己的逻辑。
我们尝试使用 SPL 来优化观察者
SPL提供了一组标准数据结构, 下面使用了观察者相关的 SplSubject、SplObserver两种接口使用方式。
Login Subject 业务主类
<?php
/**
* Created by PhpStorm.
* User: v_bivwei
* Date: 2019/9/23
* Time: 17:35
*/
class Login implements SplSubject
{
private $observers;
public function __construct()
{
$this->observers = new SplObjectStorage();
}
/**
* 加入观察者
*
* @param LoginObserverInterface $loginSubject
*/
public function attach(SplObserver $loginObserver)
{
$this->observers->attach($loginObserver);
}
/**
* 移除观察者
*
* @param LoginObserverInterface $loginObserver
*/
public function detach(SplObserver $loginObserver)
{
$this->observers->detach($loginObserver);
}
/**
* 通知观察者事件
*/
public function notify()
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
/**
* 处理登陆
*
* @return array
*/
public function handleLogin($param)
{
$isLogin = false;
//执行登陆
switch ($this->doLogin($param)) {
case 0:
$message = '登陆成功';
$isLogin = true;
break;
case 1:
$message = '帐号或密码不对';
break;
case 2:
$message = '账号已经被禁用';
break;
default:
$message = '登陆失败';
}
$this->notify();
return [
'isLogin' => $isLogin,
'message' => $message,
];
}
/**
* 执行具体登陆操作
*
* @return int
*/
public function doLogin($param)
{
//todo
return rand(0, 2);
}
}
LoginEmailNotify 邮件通知类
<?php
/**
* Created by PhpStorm.
* User: v_bivwei
* Date: 2019/9/23
* Time: 17:38
*/
class LoginEmailNotify implements SplObserver
{
private $loginSubject;
public function __construct(SplSubject $loginSubject)
{
$this->loginSubject = $loginSubject;
$this->loginSubject->attach($this);
}
/**
* Receive update from subject
* @link https://php.net/manual/en/splobserver.update.php
* @param SplSubject $subject <p>
* The <b>SplSubject</b> notifying the observer of an update.
* </p>
* @return void
* @since 5.1.0
*/
public function update(SplSubject $subject)
{
// TODO: Implement update() method.
echo "邮件登录通知 notidy " . PHP_EOL;
}
}
LoginSms 短信通知类
<?php
/**
* Created by PhpStorm.
* User: v_bivwei
* Date: 2019/9/23
* Time: 17:40
*/
class LoginSms implements SplObserver
{
private $loginSubject;
public function __construct(SplSubject $loginSubject)
{
$this->loginSubject = $loginSubject;
$this->loginSubject->attach($this);
}
/**
* Receive update from subject
* @link https://php.net/manual/en/splobserver.update.php
* @param SplSubject $subject <p>
* The <b>SplSubject</b> notifying the observer of an update.
* </p>
* @return void
* @since 5.1.0
*/
public function update(SplSubject $subject)
{
// TODO: Implement update() method.
echo "短信登录通知 notify " . PHP_EOL;
}
}
优化后的执行结果:

以上代码我们使用了php spl内部封装好的 SplSubject、SplObserver的接口,以及 SplObjectStorage 对象存储类。当然在方便的同时也带来了缺失部分灵活性,例如通知观察者只能实现 update 类接口。
1558

被折叠的 条评论
为什么被折叠?



