设计模式:观察者模式

目录

一、引言

二、优化前的代码

三、观察者模式

四、优化后的代码

五、适用场景

六、结语


一、引言

举个例子:

你关注了一个 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类想要的。

五、适用场景

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

六、结语

欢迎批评指正!


结束!

相关推荐
zhaoshuzhaoshu4 小时前
设计模式之结构型设计模式详解
python·设计模式
倒流时光三十年4 小时前
重学设计模式 之 流式 Builder 模式(Fluent Builder)
设计模式·流式 builder·fluent builder
IT枫斗者4 小时前
AI Agent 设计模式全景解析:从单体智能到分布式协作的架构演进
人工智能·redis·分布式·算法·spring·缓存·设计模式
UXbot21 小时前
AI原型设计工具评测:从创意到交互式Demo,5款产品全面解析
前端·ui·设计模式·ai·ai编程·原型模式
橘子编程1 天前
GoF 23 种设计模式完整知识总结与使用教程
java·c语言·开发语言·python·设计模式
UrSpecial1 天前
设计模式:模板方法模式
设计模式·模板方法模式
如来神掌十八式1 天前
设计模式之装饰器模式
java·设计模式
qqxhb2 天前
26|Agent 设计模式:ReAct、Plan-and-Solve 与反射
设计模式·react模式·plan-and-solve·reflection模式
hssfscv2 天前
软件设计师下午题六——Java的各种设计模式
java·算法·设计模式