【设计模式】观察者模式

一、介绍

观察者模式是一种行为设计模式,当一个对象的状态发生改变时,依赖(观察)它的对象会接收到通知,并进行自动的更新操作。

举例:某公司发布了一款新的手机,性能很强大,许多人都想买,但是该公司又没宣布售卖时间。想买的人为了第一时间就拥有这台手机,就必须每天到官网或线下实体店看有没有出售,这样对于用户来说体验很不好。如果不想频繁的去查看,这时想买手机的用户就可以在实体店或网站上留下联系方式,等到手机出售的当天公司通过邮件或者短信的形式通知到购买者。

二、优缺点

优点:

  • 符合开闭原则。 无需修改发布者代码就能引入新的观察者类 。

  • 可以在运行时建立对象之间的联系。

缺点:

  • 无法设置订阅者收到的顺序

  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率

三、核心结构

  • Subject(目标):被观察者,它是指被观察的对象。 类中有一个用来存放观察者对象的容器,这个容器是被观察者类的核心。其中还有几个方法:attach方法是向这个容器中添加观察者对象。detach方法是从容器中移除观察者对象。notify方法是依次调用观察者对象的对应方法。

  • ConcreteSubject(具体目标):目标类的具体子类,当它的状态发生改变时,向它的各个观察者发出通知。同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。

  • Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法 update()。

  • ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致,它实现了在观察者 Observer 中定义的 update()方法。

四、代码实现

1、在PHP中已经有相关的Subject(目标)和Observer(观察者)接口了,我们可以拿来直接实现。分别是SplSubject和SplObserver接口,以下代码就是以这两个接口为例进行编写。其中还用到一个SplObjectStorage类,它也是PHP中的一个类,用于存储和管理对象。它是一个关联数组,其中键是对象的哈希值,值是对象本身。

1.1、实现ConcreteSubject(具体目标)

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/17
 * Time: 10:43
 */

namespace app\admin\service\mode\observers;

use SplObserver;

/**
 * 观察者模式
 * 使用PHP自带的观察者设计模式
 */
class ObserversService implements \SplSubject
{
    public int $status;

    private $observers;

    public function __construct()
    {
        $this->observers = new \SplObjectStorage();
    }

    /**
     * 添加观察者
     * @param SplObserver $observer
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/20 17:50
     */
    public function attach(SplObserver $observer)
    {
        // TODO: Implement attach() method.
        echo "添加一个观察者\n";
        $this->observers->attach($observer);
    }

    /**
     * 删除观察者
     * @param SplObserver $observer
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/20 17:50
     */
    public function detach(SplObserver $observer)
    {
        // TODO: Implement detach() method.
        echo "\n分离一个观察者\n";
        $this->observers->detach($observer);
    }

    /**
     * 通知观察者
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/20 17:51
     */
    public function notify()
    {
        // TODO: Implement notify() method.
        echo "已通知观察者\n";
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    /**
     * 实现被观察者业务,并通知观察者
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/20 17:51
     */
    public function doSomeLogic(): void
    {
        echo "\nSubject: 我做了一些业务...\n";
        $this->status = rand(0, 10);

        echo "Subject: 业务状态小于5时观察者才做出反应,当前业务状态: {$this->status}\n";
        $this->notify();
    }
}

1.2、实现ConcreteObserver(具体观察者),我这里实现了两个观察者,分别为 ConcreteObserverB 和 ConcreteObserverA 。

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/17
 * Time: 10:54
 */

namespace app\admin\service\mode\observers;

use SplSubject;

class ConcreteObserverB implements \SplObserver
{

    public function update(SplSubject $subject)
    {
        // TODO: Implement update() method.
        if ($subject->status < 5) {
            echo "ConcreteObserverB: 我接受到状态,并做出了相应的反应。\n";
        }
    }
}

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/17
 * Time: 10:54
 */

namespace app\admin\service\mode\observers;

use SplSubject;

class ConcreteObserverA implements \SplObserver
{

    public function update(SplSubject $subject)
    {
        // TODO: Implement update() method.
        if ($subject->status < 5) {
            echo "ConcreteObserverA: 我接受到状态,并做出了相应的反应。\n";
        }
    }
}

1.3、客户端调用

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/17
 * Time: 10:34
 */

namespace app\admin\controller\mode\observers;

use app\admin\service\mode\observers\ConcreteObserverA;
use app\admin\service\mode\observers\ConcreteObserverB;
use app\admin\service\mode\observers\ObserversService;

/**
 * 观察者模式客户端调用
 */
class ObserversController
{
    public function index()
    {
        // 创建被观察者
        $subject = new ObserversService();

        // 创建观察者
        $obA = new ConcreteObserverA();
        $obB = new ConcreteObserverB();

        // 注册观察者
        $subject->attach($obA);
        $subject->attach($obB);

        // 被观察者执行业务逻辑并通知给观察者
        $subject->doSomeLogic();

        // 移除观察者
        $subject->detach($obB);

        // 被观察者执行业务逻辑并通知给观察者
        $subject->doSomeLogic();

        dd('结束');
    }
}

1.4、客户端调用结果展示

2、上面介绍了使用PHP本身观察者设计模式的接口,下面就自己手写一个观察者模式。

2.1、实现Subject(目标)接口

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/21
 * Time: 10:52
 */

namespace app\admin\service\mode\observers\my;

/**
 * 被观察者接口
 */
interface Subject
{
    /**
     * 添加观察者对象
     * @param Observer $observer    观察者对象
     * @return mixed
     * @Author: fengzi
     * @Date: 2024/5/21 10:56
     */
    public function attach(Observer $observer);

    /**
     * 删除观察者对象
     * @param Observer $observer    观察者对象
     * @return mixed
     * @Author: fengzi
     * @Date: 2024/5/21 10:56
     */
    public function detach(Observer $observer);

    /**
     * 通知观察者
     * @return mixed
     * @Author: fengzi
     * @Date: 2024/5/21 10:54
     */
    public function notify();

}

2.2、实现ConcreteSubject(具体目标)

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/21
 * Time: 10:54
 */

namespace app\admin\service\mode\observers\my;

class ConcreteSubject implements Subject
{
    public int $status;

    private array $observers = [];

    public function attach(Observer $observer)
    {
        // TODO: Implement attach() method.
        echo "添加一个观察者\n";
        if ( !in_array($observer, $this->observers, true) ) {
            $this->observers[] = $observer;
        }
    }

    public function detach(Observer $observer)
    {
        // TODO: Implement detach() method.
        echo "\n分离一个观察者:".get_class($observer)."\n";
        if ( in_array($observer, $this->observers, true) ) {
            unset($this->observers[array_search($observer, $this->observers, true)]);
        }
    }

    public function notify()
    {
        // TODO: Implement notify() method.
        echo "已通知观察者\n";
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    public function doSomething()
    {
        echo "\nSubject: 我做了一些业务...\n";
        $this->status = rand(0, 10);

        echo "Subject: 业务状态小于5时观察者才做出反应,当前业务状态: {$this->status}\n";
        $this->notify();
    }
}

2.3、实现Observer(观察者)

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/21
 * Time: 10:57
 */

namespace app\admin\service\mode\observers\my;

/**
 * 观察者接口
 */
interface Observer
{
    public function update(Subject $subject);
}

2.4、实现ConcreteObserver(具体观察者),分别为 ConcreteObserverB 和 ConcreteObserverA 。

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/21
 * Time: 10:58
 */

namespace app\admin\service\mode\observers\my;

/**
 * 具体观察者A
 */
class ConcreteObserverA implements Observer
{
    public function update(Subject $subject)
    {
        // TODO: Implement update() method.
        if ($subject->status < 5) {
            echo "ConcreteObserverA: 我接受到状态,并做出了相应的反应。\n";
        }
    }
}

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/21
 * Time: 11:06
 */

namespace app\admin\service\mode\observers\my;

class ConcreteObserverB implements Observer
{

    public function update(Subject $subject)
    {
        // TODO: Implement update() method.
        if ($subject->status < 5) {
            echo "ConcreteObserverB: 我接受到状态,并做出了相应的反应。\n";
        }
    }
}

2.5、客户端调用

<?php
/**
 * Created by PhpStorm
 * Author: fengzi
 * Date: 2024/5/17
 * Time: 10:34
 */

namespace app\admin\controller\mode\observers;

use app\admin\service\mode\observers\ConcreteObserverA;
use app\admin\service\mode\observers\ConcreteObserverB;
use app\admin\service\mode\observers\my\ConcreteSubject;
use app\admin\service\mode\observers\ObserversService;

/**
 * 观察者模式客户端调用
 */
class ObserversController
{
    /**
     * 使用PHP自带的观察者模式
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/21 11:18
     */
    public function index()
    {
        // 创建被观察者
        $subject = new ObserversService();

        // 创建观察者
        $obA = new ConcreteObserverA();
        $obB = new ConcreteObserverB();

        // 注册观察者
        $subject->attach($obA);
        $subject->attach($obB);

        // 被观察者执行业务逻辑并通知给观察者
        $subject->doSomeLogic();

        // 移除观察者
        $subject->detach($obB);

        // 被观察者执行业务逻辑并通知给观察者
        $subject->doSomeLogic();

        dd('结束');
    }

    /**
     * 使用自定义的观察者模式
     * @return void
     * @Author: fengzi
     * @Date: 2024/5/21 11:18
     */
    public function mySubject()
    {
        // 创建被观察者
        $subject = new ConcreteSubject();

        // 创建观察者
        $obA = new \app\admin\service\mode\observers\my\ConcreteObserverA();
        $obB = new \app\admin\service\mode\observers\my\ConcreteObserverB();

        // 注册观察者
        $subject->attach($obA);
        $subject->attach($obB);

        // 被观察者执行业务逻辑并通知给观察者
        $subject->doSomething();

        // 移除观察者
        $subject->detach($obB);

        // 被观察者执行业务逻辑并通知给观察者
        $subject->doSomething();

        dd('结束');
    }
}

2.6、运行结果展示

文章转载自: ++疯子丶pony++

原文链接: https://www.cnblogs.com/mklblog/p/18201411

体验地址: 引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

相关推荐
linwq82 小时前
设计模式学习(二)
java·学习·设计模式
恋猫de小郭3 小时前
Android Studio 正式版 10 周年回顾,承载 Androider 的峥嵘十年
android·ide·android studio
aaaweiaaaaaa6 小时前
php的使用及 phpstorm环境部署
android·web安全·网络安全·php·storm
工程师老罗8 小时前
Android记事本App设计开发项目实战教程2025最新版Android Studio
android
pengyu12 小时前
系统化掌握 Dart 编程之异常处理(二):从防御到艺术的进阶之路
android·flutter·dart
消失的旧时光-194312 小时前
android Camera 的进化
android
基哥的奋斗历程13 小时前
Openfga 授权模型搭建
android·adb
Pakho love1 天前
Linux:文件与fd(被打开的文件)
android·linux·c语言·c++
小王子10241 天前
设计模式Python版 桥接模式
python·设计模式·桥接模式
勿忘初心911 天前
Android车机DIY开发之软件篇(九) NXP AutomotiveOS编译
android·arm开发·经验分享·嵌入式硬件·mcu