php 设计模式之观察者模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题发生变化时,通知所有的观察对象。 总的来说, 观察者是解除耦合的重要手段
比如在一个登陆系统中,产品今天说在登陆时要加日志,明天又说在登陆时要发邮件或是发短信,后天又说要记录另外的一些东西。代码一直在打补丁,都改乱了,这时观察者模式可能会很好的解决这个问题,当登陆发生时去通知所有的观察者。
我们可以设计一下第一版的观察者模式。

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);

运行结果
file

这里有几点可以优化
  1. 我们发现了在观察者类中有一部分重复代码,每个观察者类中,就是向被观察者业务类执行 attch 操作,这部分可以抽出基类作为封装。另外一点没实现的就是 doNotify 必须作为一个参数将当前登陆的帐号或手机号传递过去, 作为发送短信依据。
  2. 我们大部分使用观察者都是使用推的模式, 被动接口 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;
    }
}

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值