一、简介

观察者模式属于行为型模式。

意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

二、建立和使用观察者模式

1、建立观察者组件

首先,我们有一个被观察的对象A,和观察对象A的观察者对象B、C、D。则我们需要

1)、声明一个抽象观察者类,类中声明观察者响应函数

2)、被观察对象A需要一个成员数据,观察者对象的指针列表_observerList,用于存放观察者们的指针。并提供在_observerList中添加、移除观察者指针的接口,即在_observerList中进行增删的方法。同时还可能需要要定义被观察事件的触发函数

3)、观察者对象B、C、D需要继承一抽象观察者类,并实现响应函数

2、使用观察者组件

1)首先,在B、C、D的对象中,要先获取到A的指针,并调用A的添加观察者指针接口,把this指针保存进A的_observerList中;

2)然后,当A中被关注的事件发生时,A根据_observerList存储的指针依次调用响应函数

3)B、C、D分别对观察的事件进行响应

三、观察者模式使用实例

假设数学教师A预计明天进行一场数学考试,同时体育老师,学生A,学生B和关注了数学考试的事件。如果将有数学考试发生时,体育老师,学生A,学生B都会收到通知。

1、首先,我们根据目前情况,先创建观察者组件

1)创建观察者抽象类EventObserver

class EventObserver
{
public:
    virtual ~EventObserver() {}

    virtual void MathExamEvent(bool isMorning) = 0;
};

        其中MathExamEvent就是定义的响应函数,此函数可以声明为纯虚函数也可以声明为虚函数,如果EventObserver有子类并不需要观察MathExamEvent事件,同时也不打算实现MathExamEvent,则可以声明为虚函数。

2)创建被观察对象。此处我们创建数学老师类

class MathTeacher
{
public:
    //添加观察者
    void addObserver(EventObserver* ob)
    {
        _mtx.lock();

        //排重处理
        if (_observerList.end() == std::find(_observerList.begin(), _observerList.end(), ob))
        {
            _observerList.push_back(ob);
        }

        _mtx.unlock();
    }

    //移除观察者
    void RemoveObserver(EventObserver* ob)
    {
        _mtx.lock();
        vector<EventObserver*>::iterator it = std::find(_observerList.begin(), _observerList.end(), ob);

        if (_observerList.end() != it)
        {
            _observerList.erase(it);
        }

        _mtx.unlock();
    }

    //数学考试
    void MathExam(bool isMorning)
    {
        cout << "明天将有一场数学考试!" << endl;

        //通知各个观察者这场考试
        _mtx.lock();

        for (auto it : _observerList)
        {
            it->MathExamEvent(isMorning);
        }

        _mtx.unlock();
    }

    MathTeacher() : _observerList(), _mtx() {}

private:
    vector<EventObserver*> _observerList;
    std::mutex _mtx; //如果是多线程编程,需要对_observerList加锁保护
};

结合上诉代码,我们可以对应到被观察类的基本组件如下:

观察者对象的指针列表:_observerList

添加、移除观察者指针的接口:addObserver、RemoveObserver

被观察事件的触发函数:MathExam

3)创建观察者实现类,需要继承观察者抽象类EventObserver,实现对MathExamEvent的响应。此处,我们声明体育老师、学生A、B三个类

//体育老师类
class PETeacher : public EventObserver
{
    virtual void MathExamEvent(bool isMorning)
    {
        cout << "我是体育老师,我了解到明天将有一场数学考试。" << endl;
    }
};

//学生A类
class StudentA: public EventObserver
{
public:
    StudentA() {}
    StudentA(MathTeacher& mathTeacher)
    {
        mathTeacher.addObserver(this);
    }

    virtual void MathExamEvent(bool isMorning)
    {
        cout << "我是小A:";
        Study();
    }

private:
    void Study()
    {
        cout << "开始认真学习!" << endl;
    }
};

//学生B类
class StudentB : public EventObserver
{
public:
    StudentB() {}
    StudentB(MathTeacher& mathTeacher)
    {
        mathTeacher.addObserver(this);
    }

    virtual void MathExamEvent(bool isMorning)
    {
        cout << "我是小B:";

        if (!isMorning)
        {
            Play();
        }

        Study();
    }

private:
    void Study()
    {
        cout << "开始认真学习!" << endl;
    }

    void Play()
    {
        cout << "开始玩耍!" << endl;
    }
};

可见,三个类都会数学考试事件MathExamEvent做出了实现,其中体育老师并没有做出响应,两位学生对这场考试分别有自己的对策。学生A打算立刻学习,准备考试;学生B认为如果考试在下午,可以先玩耍一会儿。

【注意】

        一般情况下,我们要避免各观察者类在观察事件响应函数中做太多的事情,已防止阻塞对其他观察者响应函数的调用和触发函数的继续运行。如果某一观察者类的响应确实十分繁琐,必要情况下我们可以抛线程执行。

        比如,以上诉考试事件为例,因为“数学老师”是依次通知各观察者的,假如通知“体育老师”的时候,体育老师为了详细了解这场考试的情况,和“数学老师”聊了大半天并当场做出一大堆计划,则数学老师资源无法释放,会妨碍学生A,B得到这场考试的通知,同时数学老师也无法做其他事情。此时,体育老师可以先停止和数学老师的沟通,释放数学老师后,自己做出处理。见下列代码

体育老师需要做出计划

//体育老师类
class PETeacher : public EventObserver
{
    virtual void MathExamEvent(bool isMorning)
    {
        cout << "我是体育老师,我了解到明天将有一场数学考试。" << endl;
        DoPlan();
    }

private:
    void DoPlan()
    {
        cout << "体育老师:结合相关信息,做出上课计划!" << endl;
        int plan = 100;

        for (int i = 0; i < 100; i++)
        {
            cout << "体育老师:计划" << i << "开始" << endl;
        }
    }
};

由于计划十分繁琐,我们可以抛线程执行,不阻塞主线程

//体育老师类
class PETeacher : public EventObserver
{
public:
    virtual void MathExamEvent(bool isMorning)
    {
        cout << "我是体育老师,我了解到明天将有一场数学考试。" << endl;
        //抛线程执行DoPlan
        threadObj = new thread(PETeacher::DoPlan);
    }

    static void DoPlan()
    {
        cout << "体育老师:结合相关信息,做出上课计划!" << endl;
        int plan = 100;

        for (int i = 0; i < 100; i++)
        {
            cout << "体育老师:计划" << i << "开始" << endl;
        }
    }

    PETeacher() : threadObj(nullptr) {}

    ~PETeacher()
    {
        if (nullptr != threadObj)
        {
            delete threadObj;
            threadObj = nullptr;
        }
    }

private:
    std::thread* threadObj;
};

2、观察者创建完毕,接下来展示的是对观察者的使用代码

int main()
{
    //创建实例
    MathTeacher mathTeacher;

    PETeacher peTeacher;
    //体育老师构造后,调用数学老师的addObserver方法添加观察者
    mathTeacher.addObserver(&peTeacher);

    //构造函数中添加观察者
    StudentA stdA(mathTeacher);
    StudentB stdB(mathTeacher);

    //数学老师计划组织数学考试
    mathTeacher.MathExam(true);

    system("pause");
    return 0;
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐