设计模式:观察者模式

目录

一、引言

二、优化前的代码

三、观察者模式

四、优化后的代码

五、适用场景

六、结语


一、引言

举个例子:

你关注了一个 UP 主,UP 主更新视频,所有关注他的粉丝都会收到推送通知。

怎么写代码?

二、优化前的代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>

class Fan {
public:
    Fan(const std::string& name):_name(name){}
    void update(const std::string& msg) {
        std::cout << _name << ":" << msg << std::endl;
    }
private:
    std::string _name;
};

class UPOwner {
public:
    void addFan(Fan* f) { _fans.push_back(f); }
    void releaseVideo(const std::string& v) {
        std::cout << "\nUP更新:" << v << std::endl;
        for (auto f : _fans) f->update("看新视频:" + v);
    }
private:
    std::vector<Fan*> _fans;
};

int main() {
    UPOwner up;
    Fan f1("张三");
    Fan f2("李四");

    up.addFan(&f1);
    up.addFan(&f2);

    up.releaseVideo("C++教程");
    
    return 0;
}

UPOwner类内部维护一个粉丝列表,通过addFan()方法将粉丝添加到列表中,当发布新的视频时,遍历粉丝列表通知所有的粉丝。

毫无疑问,它完成了我们提出的需求。

但是,它只能通知Fan类型,如果要新增一个系统推送类型,那么就需要在UPOwner内部添加一个新的列表来维护新的类型。

这就违反了开闭原则,代码的灵活性和扩展性不高。

三、观察者模式

观察者模式的定义:

观察者模式定义对象间的一对多依赖关系,使得每当一个对象状态发生改变时,其所有依赖者都会收到通知并自动更新。

观察者模式,也叫做发布-订阅模式。多个客户端订阅同一个"体育消息"主题时,当这个主题中有新消息到来后,就会自动广播发送给所有的订阅客户端。

在观察者模式中,有四个角色。

  1. Observer:观察者的抽象类,定义一个统一的更新接口,比如上面代码Fan类中的update。
  2. ConcreteObserver:具体观察者,继承了抽象观察者类,实现父类中的update方法。
  3. Subject:抽象主题类,提供注册、移除、通知观察者的接口,内部维护一个观察者列表,消息到来时,遍历列表统一发送通知。
  4. ConcreteSubject:具体主题类,也就是继承了抽象主题的类。

看到这里,估计还是一知半解,没关系,我们先看代码,结合优化后的代码来理解。

四、优化后的代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>

// 抽象观察者类
class Observer {
public:
    virtual void update(const std::string& msg) = 0; 
    virtual ~Observer() = default;
};
// 抽象主题类
class Subject {
public:
    // 注册接口,向列表中添加观察者
    void attach(Observer* observer) {
        _observers.push_back(observer);
    }
    // 移除接口,从列表中移除观察者,消息推送将不再通知它
    void detach(Observer* observer) {
        for (auto it = _observers.begin(); it != _observers.end(); it++) {
            if (*it == observer) {
                _observers.erase(it);
                break;
            }
        }
    }
    // 广播接口,主题收到新的消息后,通过广播发送给所有订阅的观察者
    void notify(const std::string& msg) {
        for (Observer* obs : _observers) {
            obs->update(msg);
        }
    }
    virtual ~Subject() = default;
private:
    std::vector<Observer*> _observers;
};
// 具体主题类,主题类型为UP主
class UPOwner : public Subject {
public:
    // 发布视频
    void releaseVideo(const std::string& video_name) {
        _latestVideo = video_name;
         std::cout << "\n[UP主] 发布了新视频:" << video_name << std::endl;
        // 主动通知所有粉丝
        notify("您关注的UP主更新了:" + _latestVideo);
    }
private:
    std::string _latestVideo;
};
// 具体观察者类,粉丝类
class Fan : public Observer {
public:
    Fan(const std::string& name):_fan_name(name){}
    // 实现父类中的抽象方法,描述收到推送来的消息后的处理逻辑
    virtual void update(const std::string& msg) {
        std::cout << "[粉丝 " << _fan_name << "] 收到通知:" << msg << std::endl;
    }
private:
    std::string _fan_name;
};

int main() {
    // 1. 创建主题(UP主)
    UPOwner up;

    // 2. 创建观察者(粉丝)
    Fan fan1("张三");
    Fan fan2("李四");
    Fan fan3("王五");

    // 3. 粉丝关注UP主(注册观察者)
    up.attach(&fan1);
    up.attach(&fan2);
    up.attach(&fan3);

    // 4. UP主发布视频,自动通知所有粉丝
    up.releaseVideo("C++ 观察者模式精讲");

    // 5. 粉丝取消关注
    up.detach(&fan2);
    std::cout << "\n--- 粉丝李四取消关注了 ---\n" << std::endl;

    // 6. UP主再次发布视频
    up.releaseVideo("C++ 设计模式实战");

    return 0;
}

引入了抽象的观察者类和抽象的主题类。有什么用?

这时,观察者的类型可以不再只是Fan类型,任何类型都可以,只要它继承抽象观察者类,主题类的列表中,就可以把它添加进去。

而且,主题类也可以不只是UP主,任何类型都可以,继承抽象主题类就行。

这样做毫无疑问提高了代码的扩展性。

我们看看它都符合哪些设计原则。

  1. 开闭原则:新增其他主题类型时,只要这个类型继承抽象主题类,实现发布接口就可以了,Subject类内部不需要修改任何代码。
  2. 单一职责:观察者只需要实现一个update方法,只负责接收并处理更新。
  3. 接口隔离:接口隔离说的是,子类不应该依赖它不需要的接口。上面代码中,子类只有一个方法update,而且不是多余的。简单来说,就是谁要用什么,就给它什么,别给多余的。

这里举一个违反接口隔离原则的例子:

cpp 复制代码
class Animal {
public:
    virtual void run() = 0;
    virtual void fly() = 0;
};

class Dog : public Animal {
public:
    virtual void run() {
        //跑......
    }
    virtual void fly() {
        // 飞......
    }
};

Dog类继承Animal类,Dog类除了要实现run(跑)方法,还要实现fly(飞)方法,可是,狗是不会飞的!这就违背了接口隔离原则------fly方法不是Dog类想要的。

五、适用场景

主题消息的发布-订阅场景下,就很适合使用观察者模式。

六、结语

欢迎批评指正!


结束!

相关推荐
阿坤带你走近大数据42 分钟前
分别介绍下java主流的开发框架、设计模式与对应编程语言的高级特性
java·开发语言·设计模式
geovindu1 小时前
go: Coroutines Pattern
开发语言·后端·设计模式·golang·协程模式
Anastasiozzzz2 小时前
构建健壮软件系统的基石:深入解析面向对象设计七大原则
开发语言·javascript·设计模式·ecmascript
qq_2975746721 小时前
设计模式系列文章(基础篇第19篇):中介者模式——封装交互关系,解耦网状依赖
设计模式·交互·中介者模式
AI大法师1 天前
老牌媒体怎么从“出版物更新”走到“品牌系统升级”
大数据·人工智能·设计模式·新媒体运营
野生技术架构师1 天前
Java 23 种设计模式:从踩坑到精通 —— 开篇及系列介绍
java·开发语言·设计模式
艾利克斯冰1 天前
Java设计模式-创建型模式(更新完成)
设计模式
王_teacher1 天前
23种设计模式之工厂模式
设计模式·软件工程·简单工厂模式·工厂方法模式·抽象工厂模式
geovindu1 天前
python:Coroutines Pattern
开发语言·python·设计模式·协程模式
sycmancia1 天前
Qt——模型视图设计模式
设计模式