Skip to content

1. 观察者模式

想象有这样一个场景,有一个公司类 class observer,有一个员工类 class subject,二者内部存在一个函数,用来打印一些东西。规定员工在群里一句话,都要在这句话前面加上公司的水印(公司先打印一句话),表明该员工属于该公司。如果员工离职了,员工就不能在群里说话了。为了方便起见,定义一个函数指针,用来完成打印操作。

*typedef void(callback)(const std::string&);

  • 定义公司类和员工类,并在内部实现其打印的功能
cpp
// 公司类  
class observer  
{  
public:  
    //接受一个 callback 类型的参数,并将其保存到 callback 成员变量中  
    observer(callback callback) : callback(callback) {}  

void print(const std::string& message)  
{  
    if (callback)  
    {  
    //函数指针调用  
    callback(message);  
    }  
}  

private:  
    //类内部存在一个函数指针  
    callback callback;  
};  

// 员工类  
class subject  
{  
public:  
    Subject(callback callback) : callback(callback) {}  

void print(const std::string& message)  
{  
    if (callback)  
    {  
    callback(message);  
    }  
}  

private:  
    //类内部存在一个函数指针  
    callback callback;  
};
  • 员工加入公司,公司获得员工发状态在职或离职(公司类)
cpp
//获取保存的函数指针。
callback getCallback() const
{
    return callback;
}
  • 员工离职后不能再在群里说话,即打印函数失效,函数指针赋空(员工类)
cpp
void detach()
{
    callback = NULL;
}
  • 定义打印函数
cpp
//打印函数
void observerFunc(const std::string& message)
{
    std::cout << message << std::endl;
}
  • 在main函数中定义公司类和员工类并验证
cpp
int main()
{
    observer observer(observerFunc);
    observer.print("observer function received message:");
    subject subject(observer.getCallback());
    subject.print("hello world");
    return 0;
}
  • 此时员工离职
cpp
subject.detach();
observer.print("observer function received message:");
subject.print("hello world");

此时运行在问题,员工离职,但是公司不知道,导致公司多打印了一句话 observer function received message:

  • 完整代码
cpp
#include <iostream>

//这里使用 typedef 或 using 关键字定义了一个函数指针类型 callback,用于接收带有 std::string 参数并返回 void 的函数指针。
typedef void(*Callback)(const std::string&);

// 公司类
class observer
{
    public:
    //接受一个callback类型的参数,并将其保存到callback成员变量中
    observer(callback callback) : callback(callback) {}

    //公司调用打印函数
    void print(const std::string& message)
    {
    if (callback)
    {
    callback(message);
    }
    }
    //获取保存的函数指针。
    callback getCallback() const
    {
    return callback;
    }

    private:
    //类内部存在一个函数指针
    callback callback;
};

    //员工类
    class Subject
    {
    public:
    //接受一个callback类型的参数,并将其保存到callback成员变量中
    Subject(callback callback) : callback(callback) {}
    void print(const std::string& message)
    {
    //员工调用打印函数
    if (callback)
    {
    callback(message);
    }
    }
    //员工离职
    void detach()
    {
    callback = nullptr;
    }
    private:
    //类内部存在一个函数指针
    callback callback;
};

    //打印函数
    void observerFunc(const std::string& message)
    {
    std::cout << message << std::endl;
    }
    
int main()
{
    Observer observer(observerFunc);
    observer.print("Observer function received message:");

    Subject subject(observer.getCallback());
    subject.print("hello world");

    subject.detach();
    observer.print("Observer function received message:");
    subject.print("hello world");

    return 0;
}

为了能够让公司知道员工离职,需要定义一系列方法,如员工状态、获取员工状态等。修改后的完整代码如下:

cpp
#include <iostream>

//这里使用 typedef 或 using 关键字定义了一个函数指针类型 callback,用于接收带有 std::string 参数并返回 void 的函数指针。
typedef void(Callback)(const std::string&);

//员工状态
enum Status
{
    onEmployeed,
    offEmployeed
};

class Subject;

// 公司类
class observer
{
public:
    //接受一个 callback 类型的参数,并将其保存到 callback 成员变量中
    observer(Callback callback) : callback(Callback) {}

    void print(const std::string& message)
    {
    if (callback)
    {
    callback(message);
    }
    }

    const status getSubjectStatus(const Subject& subject);

    ///获取保存的函数指针。
    callback getCallback() const
    {
    return callback;
    }
}

private:
    //类内部存在一个函数指针
    callback callback;
};

// 员工类
class Subject
{
    public:
    Subject(Callback callback, status status = offEmployeed) :
    callback(callback), status(status) {}
    void print(const std::string& message)
    {
    if (callback)
    {
    callback(message);
    }
    }
    void detach()
    {
    callback = nullptr;
    status = offEmployeed;
    }
    friend class Observer;
    private:
    // 类内部存在一个函数指针
    callback callback;
    Status status;
};

const Status Observer::getSubjectStatus(const Subject& subject)
{
    return subject.status;
}

// 打印函数
void observerFunc(const std::string& message)
{
    std::cout << message << std::endl;
}

int main()
{
    observer observer(observerFunc);
    Subject subject(observer.getCallback(), onEmployeed);
    // 只有在职时员工说话,才加上公司水印,否则不输出
    if (observer.getSubjectStatus(subject) == onEmployeed)
    {
    observer.print("Observer function received message:");
    subject.print("hello world");
    }

    subject.detach(); // 员工离职
    if (observer.getSubjectStatus(subject) == onEmployeed)
    {
    observer.print("Observer function received message:");
    subject.print("hello world");
    }

    // 新员工入职
    subject subject1(observer.getCallback(), onEmployeed);
    if (observer.getSubjectStatus(subject1) == onEmployeed)
    {
    observer.print("observer function received message:");
    subject1.print("hello world");
    }

    return 0;
}

此时可以完成相关功能。

但是该代码存在一些问题:

  1. 耦合度高。公司每次在打印之前,都要去获取员工的状态。换句话说,公司是否能打印,取决于员工的状态。
  2. 友元破坏了类的封装性,将员工类将公司类声明为自己的友元,公司可以访问员工在职状态外,还可获取员工类其他参数。
  3. 不能实时更新,员工离职后,明明已经获取到了员工离职的状态,但并没有更新,后续该员工还要打印,还需再判断。

针对上述的这些缺点,我们可以使用一种新的模式解决,即观察者模式

1.1 观察者模式的定义

  • 定义
    观察者模式是一种行为设计模式,它定义了一种一对多的依赖关系,当一个对象(被观察者)的状态发生变化时,它会自动通知并通知所有依赖它的对象(观察者),使得这些观察者能够自动更新自己的状态。

在观察者模式中,有两个主要角色:

  1. 被观察者(Subject/Observable):也称为主题,它维护一组观察者对象,并提供了用于添加、删除和通知观察者的方法。当被观察者的状态发生变化时,它会调用观察者的特定方法来通知它们。
  2. 观察者(Observer):也称为订阅者,它定义了一个接口或抽象类,用于接收被观察者的通知,并进行相应的处理。观察者可以让注册到被观察者中,以便在被观察者状态变化时接收通知并更新自己。

观察者模式的核心思想是解释被观察者和观察者之间的依赖关系,使得它们能够独立地变化。被观察者只需要关注观察者的接口,而不需要知道观察者的具体实现。这种松耦合的设计使得系统更加灵活、可扩展和易于维护。

1.2 观察者模式实现

  • 在观察者类中,代码基本不用变,具体的处理,通过函数指针来实现
cpp
// 观察者接口
class observer
{
public:
    //接受一个callback类型的参数,并将其保存到callback成员变量中
    observer(callback callback) : callback(callback) {}

    //获取保存的函数指针。
    callback getcallback() const
    {
    return callback;
    }

    //void setcallback(callback callback)
    //{
    this->callback = callback;
    //}
private:
    //类内部存在一个函数指针,通过函数指针完成删除、通知订阅者消息的操作
    callback callback;
};

在被观察类中,定义三个函数,加入公司、离职和通知函数,在通知函数中打印消息,并通过函数指针传递消息,于此同时,不再需要成员变量来设置员工状态

cpp
// 被观察者
class Subject
{
public:
    //接受一个callback类型的参数,并将其保存到callback成员变量中
    void attach(callback callback)
    {
    this->callback = callback;
    }
    //将callback设置为nullptr,即取消观察者的绑定
    void detach()
    {
    callback = nullptr;
    }
    //通知观察者,如果callback不为nullptr,则调用函数指针并传递消息。
    void notify(const std::string& message)
    {
    if (callback)
    {
    callback(message);
    }
    }

private:
    //类内部存在一个函数指针,通过函数指针完成删除、通知订阅者消息的操作
    callback callback;
};

观察者函数,由于观察者类中不再打印,因此此函数作为观察者专用函数

cpp
// 观察者函数
//接收一个 std::string 类型的参数,并在控制台打印消息。
void observerFunc(const std::string& message)
{
    std::cout << "Observer function received message: "<< message << std::endl;
}

此时情况便变得简单很多,完整代码如下

cpp
#include <iostream>
//这里使用 typedef 或 using 关键字定义了一个函数指针类型 callback,用于接收带有 std::string 参数并返回 void 的函数指针。
typedef void(*Callback)(const std::string&);
//using callback = void (*)(const std::string&);

// 观察者接口
class observer
{
public:
    //接受一个 callback 类型的参数,并将其保存到 callback 成员变量中
    observer(callback callback) : callback(callback) {}

    //获取保存的函数指针。
    callback getCallback() const
    {
    return callback;
    }

    //void setCallback(callback callback)
    //
    this->callback = callback;
    //
private:
    //类内存储在一个函数指针,通过函数指针完成删除、通知订阅者消息的操作
    callback callback;
};

// 被观察者
class Subject
{
public:
    //接受一个 callback 类型的参数,并将其保存到 callback 成员变量中
    void attach(callback callback)
    {
    this->callback = callback;
    }
    //将 callback 设置为 nullptr,即取消观察者的绑定
    void detach()
    {
    callback = nullptr;
    }
    //通知观察者,如果 callback 不为 nullptr,则调用函数指针并传递消息。
    void notify(const std::string& message)
    {
    if (callback)
    {
    callback(message);
    }
    }
private:
    // 类内部存在一个函数指针,通过函数指针完成删除。通知订阅者消息的操作
    callback callback;
};

// 观察者函数
// 接收一个 std::string 类型的参数,并在控制台打印消息。
void observerFunc(const std::string& message)
{
    std::cout << "observer function received message: " << message << std::endl;
}

/// 观察者函数
/// 接收一个 std::string 类型的参数,并在控制台打印消息。
// void observerFunc1(const std::string& message) {
    // std::cout << "New observer function received message: " << message << std::endl;
    // }

int main()
{
    // 被观察者对象
    Subject subject;
    // 观察者对象
    // 并将观察者函数 observerFunc 的函数指针传递给 observer 对象的构造函数
    observer observer(observerFunc);
    // 将观察者绑定到被观察者对象中
    subject.attach(observer.getCallback());
    // 通知观察者
    subject.notify("Hello, world!");
    // 取消观察者的绑定
    subject.detach();
    // 验证观察者是否被正确地解除绑定
    subject.notify("How are you?");
    return 0;
}

更换公司、新员工入职也变得简单起来

cpp
#include <iostream>

//这里使用 typedef 或 using 关键字定义了一个函数指针类型 callback,用于接收带有 std::string 参数并返回 void 的函数指针。
typedef void(*Callback)(const std::string&);
//using callback = void (*)(const std::string&);

// 观察者接口
class observer
{
public:
    //接受一个 callback 类型的参数,并将其保存到 callback 成员变量中
    observer(callback callback) : callback(callback) {}

    //获取保存的函数指针。
    callback getcallback() const
    {
    return callback;
    }

    //更换公司
    void setcallback(callback callback)
    {
    this->callback = callback;
    }
private:
    //类内都存在一个函数指针,通过函数指针完成删除、通知订阅者消息的操作
    callback callback;
};

// 被观察者
class subject
{
public:
    //接受一个 callback 类型的参数,并将其保存到 callback 成员变量中
    void attach(callback callback)
    {
    this->callback = callback;
    }
    //将 callback 设置为 nullptr,即取消观察者的绑定
    void detach()
    {
    callback = nullptr;
    }
    //通知观察者,如果 callback 不为 nullptr,则调用函数指针并传递消息。
    void notify(const std::string& message)
    {
    if (callback)
    {
    callback(message);
    }
    }
private:
    //类内都存在一个函数指针,通过函数指针完成删除、通知订阅者消息的操作
    callback callback;
};

// 观察者函数
//接收一个 std::string 类型的参数,并在控制台打印消息。
void observerFunc(const std::string& message)
{
    std::cout << "Observer function received message: " << message << std::endl;
}

// 观察者函数(新公司)
//接收一个 std::string 类型的参数,并在控制台打印消息。
void observerFunc1(const std::string& message) {
    std::cout << "New observer function received message: " << message << std::endl;
}

int main()
{
    //观察者对象
    //并将观察者函数 observerFunc 的函数指针传递给 observer 对象的构造函数
    observer observer(observerFunc);
    //被观察者对象
    subject subject;
    //将观察者绑定到被观察者对象中
    subject.attach(observer.getcallback());
    //通知观察者
    subject.notify("Hello, world!");
    //取消观察者的绑定
    subject.detach();
    //验证观察者是否被正确地解除绑定
    subject.notify("How are you?");

    //更换新公司
    observer.setcallback(observerFunc1);
    subject.attach(observer.getcallback());
    subject.notify("Hello, world!");
    //新员工入职
    subject subject1;
    subject1.attach(observer.getcallback());
    subject1.notify("Hello, world!");
    return 0;
}

这就是观察者模式,通过函数指针传递消息。

  • (观察者):订阅者定义一个接口,用于接收被观察者发出的新闻件通知(状态改变)。
  • (被观察者):邮件发布者(例如,邮件服务提供商)负责维护一组订阅者,并提供注册、删除和通知订阅者的方法。

当然,使用函数指针作为观察者和被观察者之间沟通的桥梁(即具有相同的函数指针)
观察者模式也可以不必使用相同的成员,此时则需要观察者和被观察者之间进行绑定。

举例:周末孩子在家学习,妈妈去上班。孩子想玩游戏,所以看到妈妈出门后即开始玩游戏,看到妈妈开门就开始学习

cpp
#include <iostream>
using namespace std;

// 前置声明
class child;
// ---
// 母亲类:被观察者
class Mother
{
public:
    Mother();
    ~Mother();

    void AddChild(child* c); // 添加观察者
    void DeLChild(child* c); // 移除观察者
    void Notify(string action); // 通知

private:
    // 一个被观察者:可以拥有多个观察者。
    child* list[10]; // 目前我们通过数组来实现观察者集合
};

class child
{
public:
    child(string name, Mother* m);
    ~child();
    string getName();
    // 状态切换的接口
    void Update(string action);
private:
    Mother* myMom;
    string name;
};

//---实现---
// mother类的成员函数的实现
Mother::Mother()
{
    for (int i = 0; i < 10; i++)
    list[i] = NULL;
}

Mother::~Mother()
{
}
void Mother::AddChild(child* c) // 添加观察者
{
    // 遍历孩子数组,找一个空位置来添加
    for (int i = 0; i < 10; i++)
    {
    // 判断当前的位置是否为空
    if (list[i] == NULL)
    {
        list[i] = c; // 添加观察者
        cout << c->getName() << "被添加到了观察者集合" << endl;
        break;
    }
    }
}

void Mother::DelChild(child* c) // 移除观察者
{
    // 遍历孩子数组,找一个到需要移除的观察者
    for (int i = 0; i < 10; i++)
    {
    if (list[i] == c) // 判断当前的位置是否和指针指向同一个空间
    {
        cout << list[i]->getName() << "被移除了观察者集合" << endl;
        list[i] = NULL; // 移除观察者
        break;
    }
    }
}

void Mother::Notify(string action) // 通知
{
    // 遍历孩子数组,逐个的将每个观察者都通知到
    for (int i = 0; i < 10; i++)
    {
    if (list[i] != NULL)
    list[i]->update(action);
    }
}

child::child(string name, Mother* m)
    :myMom(m), name(name)
{
    myMom->AddChild(this);
}

child::~child()
{
    myMom->DelChild(this);
}

string child::getName()
{
    return name;
}

// 状态切换的接口
void child::Update(string action)
{
    std::cout << name << "观察到:" << action << ": ";
    if (action == "妈妈在开门!")
    {
    std::cout << "关闭电脑,开启疯狂学习模式!" << std::endl;
    }
    else if (action == "妈妈出门了!")
    {
    std::cout << "打开电脑,开启疯狂游戏模式!" << std::endl;
    }
    else
    {
    std::cout << "没事!" << std::endl;
    }
}

int main()
{
    Mother m; // 创建一个被观察者

    child* child1 = new child(string("老六"), &m);
    child* child2 = new child(string("老七"), &m);

    m.Notify("妈妈在开门!");

    delete child2;

    m.Notify("妈妈出门了!");

    return 0;
}

观察者模式也可为一个观察者绑定多个被观察者

cpp
#include <iostream>
using namespace std;

// 前置声明
class Child;
//---声明---
// 母亲类:被观察者
class Mother
{
public:
    Mother();
    ~Mother();

    void AddChild(child *c); // 添加观察者
    void DelChild(child *c); // 移除观察者
    void Notify(string action); // 通知

private:
    // 一个被观察者:可以拥有多个观察者。
    child *list[10]; // 目前我们通过数据来实现观察者集合
};

// 父亲类:被观察者
class Father
{
public:
    Father();
    ~Father();

    void AddChild(Child *c); // 添加观察者
    void DelChild(Child *c); // 移除观察者
    void Notify(string action); // 通知

private:
    // 一个被观察者,可以拥有多个观察者。
    child *list[10]; // 目前我们通过数组来实现观察者集合
};

class child
{
    public:
    child(string name);
    ~child();
    string getName();
    // 状态切换的接口
    void Update(string action);
    private:
    string name;
};

//---实现---
// mother类的成员函数的实现
Mother::Mother()
{
    for(int i = 0;i < 10;i++)
    list[i] = NULL;
}

Mother::~Mother()
{
}
void Mother::AddChild(Child *c) // 添加观察者
{
    // 遍历孩子数组,找一个空位置来添加
    for(int i = 0;i < 10;i++)
    {
    // 判断当前的位置是否为空
    if(list[i] == NULL)
    {
        list[i] = c; // 添加观察者
        cout << c->getName() << "被添加到了观察者集合" << endl;
        break;
    }
    }
}

void Mother::DelChild(Child *c) // 移除观察者
{
    // 遍历孩子数组,找一个到需要移除的观察者
    for(int i = 0;i < 10;i++)
    {
    // 判断当前的位置是否和指针指向同一个空间
    if(list[i] == c)
    {
        cout << list[i]->getName() << "被移除了观察者集合" << endl;
        list[i] = NULL; // 移除观察者
        break;
    }
    }
}

void Mother::Notify(string action) // 通知
{
    // 遍历孩子数组,逐个的将每个观察者都通知到
    for(int i = 0;i < 10;i++)
    {
    if(list[i] != NULL)
    list[i]->update(action);
    }
}

Father::Father()
{
    for(int i = 0;i < 10;i++)
    list[i] = NULL;
}

Father::~Father()
{
}
void Father::AddChild(child *c) // 添加观察者
{
    // 遍历孩子数组,找一个空位置来添加
    for(int i = 0;i < 10;i++)
    {
    // 判断当前的位置是否为空
    if(list[i] == NULL)
    {
        list[i] = c; // 添加观察者
        cout << c->getName() << "被添加到了观察者集合" << endl;
        break;
    }
    }
}

void Father::DelChild(child *c) // 移除观察者
{
    // 遍历孩子数组,找一个到需要移除的观察者
    for(int i = 0;i < 10;i++)
    {
    // 判断当前的位置是否和指针指向同一个空间
    if(list[i] == c)
    {
        cout << list[i]->getName() << "被移除了观察者集合" << endl;
        list[i] = NULL; // 移除观察者
        break;
    }
    }
}

void Father::Notify(string action) // 通知
{
    // 遍历孩子数组,逐个的将每个观察者都通知到
    for(int i = 0; i < 10; i++)
    {
    if(list[i] != NULL)
    list[i]->update(action);
    }
}

child::child(string name):name(name)
{
}

child::~child()
{
}

string child::getName()
{
    return name;
}

// 状态切换的接口
void child::update(string action)
{
    std::cout << name << "观察到:" << action << "::";
    if(action == "妈妈在开门!" || action == "爸爸下班了!")
    {
    std::cout << "关闭电脑,开启疯狂学习模式!" << std::endl;
    }
    else if(action == "妈妈出门了!" || "爸爸上班了!")
    {
    std::cout << "打开电脑,开启疯狂游戏模式!" << std::endl;
    }
    else {
    std::cout << "没事!" << std::endl;
    }
}

int main()
{
    Mother m; // 创建一个被观察者
    Father f; // 创建一个被观察者

    child c1("老六");
    child c2("老七");

    m.AddChild(&c1);
    m.AddChild(&c2);
    f.AddChild(&c2);

    m.Notify("妈妈出门了!");
    f.Notify("爸爸上班了!");
    m.Notify("妈妈在开门!");
    f.DelChild(&c2);
    f.Notify("爸爸下班了!");

    return 0;
}

1.3 观察者模式的优点

解耦: 观察者模式可以将被观察者与观察者解耦,使它们能够独立演化。被观察者只需要维护一个观察者列表,并在状态改变时通知观察者,而不需要了解观察者的具体实现。

灵活性和可扩展性: 使用观察者模式可以方便地添加新的观察者,或者移除现有的观察者,而不需要修改被观察者的代码。这提供了灵活性和可扩展性,使系统能够适应变化的需求。

发布-订阅机制: 观察者模式提供了一种发布-订阅机制,使得一个对象的状态变化可以被多个其他对象订阅并进行相应的处理。这种机制能够实现依赖的事件驱动架构,提高系统的可维护性和可重用性。

实时更新: 观察者模式可以实现实时的更新机制,当被观察者的状态发生变化时,观察者可以立即进行响应。这在需要保持数据一致性和实时性的应用场景中非常有用,例如图形用户界面、消息传递系统等。

知识如风,常伴吾身