观察者的实现和填坑

观察者模式在工程项目里具有非常广泛的应用,本文聊一聊自己对观察者模式的一些理解和实现。
按照我自己的风格,我非常不赞同讲各种原理看起来深入浅出头头是道最后就贴几句伪代码甚至伪代码都没有就完事的博客方式。既然写在博客上了,就是为了供他人讨论和借鉴。简而言之,上代码,讲道理。

最简单的观察者实现

#include <iostream>
#include <list>
using namespace std;

#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>

class Subject;

class IObserver
{
public:
    virtual void OnTrigger(Subject *subject) = 0;
};

class Subject
{
    typedef list<IObserver *>::iterator ListIter;
public:
    void Atatch(IObserver *obs)
    {
        boost::mutex::scoped_lock lock(m_mutex);

        m_ptrObservers.push_back(obs);
    }

    void Detach(IObserver *obs)
    {
        boost::mutex::scoped_lock lock(m_mutex);

        ListIter it = find(m_ptrObservers.begin(), m_ptrObservers.end(), obs);
        if (it != m_ptrObservers.end())
        {
            m_ptrObservers.erase(it);
        }
    }

    void Trigger()
    {
        boost::mutex::scoped_lock lock(m_mutex);

        for (ListIter it = m_ptrObservers.begin(); it != m_ptrObservers.end(); ++it)
        {
            (*it)->OnTrigger(this);
        }
    }
private:
    boost::mutex m_mutex;
    list<IObserver *> m_ptrObservers;
};

class Observer : public IObserver
{
public:
    void OnTrigger(Subject *subject)
    {
        cout << "Observer:OnTrigger" << endl;
    }
};

int main()
{
    vector<IObserver *> vecObs;
    Subject subject;
    for (int i = 0; i < 10; ++i)
    {
        IObserver *ptrObs = new Observer();
        subject.Atatch(ptrObs);
        vecObs.push_back(ptrObs);
    }

    subject.Trigger();

    {
        // todo delete here 
    }
    return 0;
}

当然这其中的问题是多方面的,显然,在Trigger里面,(*it)->OnTrigger(this),如果观察者在自己的实现中做了Detach或者Atach其他指针的操作,那么死锁是不可避免的了。因此最好使用递归锁:

boost::recursive_mutex m_mutex;
上锁时:
boost::recursive_mutex::scoped_lock lock(m_mutex);

这样是不是问题就解决了呢?显然问题仍然非常严重:由于Detach或者Atach操作修改了容器内的结构,因此在

for (ListIter it = m_ptrObservers.begin(); it != m_ptrObservers.end(); ++it)
{
    (*it)->OnTrigger(this);
}

这段代码时,迭代器失效会直接导致程序的崩溃。
如何解决这样的问题,这里就需要一些技巧了。

计数机制避免迭代器失效

#include <iostream>
#include <list>
using namespace std;

#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>

class Subject;

class IObserver
{
public:
    virtual void OnTrigger(Subject *subject) = 0;
};

class Subject
{
    typedef list<IObserver *>::iterator ListIter;
public:
    Subject()
    {
        m_nUseCount = 0;
    }

    void Atatch(IObserver *obs)
    {
        boost::recursive_mutex::scoped_lock lock(m_mutex);
        ++m_nUseCount;

        if (m_nUseCount == 1)
        {
            m_ptrObservers.push_back(obs);
        }
        else
        {
            // 本类里面使用的list,push_back不会迭代器失效,但是如果使用vector等其他容器,就会存在问题了
            // 这时候先放入缓冲区里面,防止Trigger处失效
            m_ptrToAdd.push_back(obs);
        }

        --m_nUseCount;
    }

    void Detach(IObserver *obs)
    {
        boost::recursive_mutex::scoped_lock lock(m_mutex);
        ++m_nUseCount;

        ListIter it = find(m_ptrObservers.begin(), m_ptrObservers.end(), obs);
        if (it != m_ptrObservers.end())
        {
            if (m_nUseCount == 1)
                m_ptrObservers.erase(it); // 说明只有当前处使用了m_ptrObservers,可以放心删除
            else
                *it = NULL;
        }

        --m_nUseCount;
    }

    void Trigger()
    {
        boost::recursive_mutex::scoped_lock lock(m_mutex);
        ++m_nUseCount;

        // 先检查一下缓冲区
        if (m_nUseCount == 1 && !m_ptrToAdd.empty())
        {
            for (ListIter it = m_ptrToAdd.begin(); it != m_ptrToAdd.end(); ++it)
            {
                m_ptrObservers.push_back(*it);
            }
            m_ptrToAdd.clear();
        }

        for (ListIter it = m_ptrObservers.begin(); it != m_ptrObservers.end(); )
        {
            if (*it)
            {
                (*it)->OnTrigger(this);
                ++it;
            }
            else
            {
                if (m_nUseCount == 1)
                {
                    // 这时候确定可以删除了
                    m_ptrObservers.erase(it++);
                }
            }
        }

        --m_nUseCount;
    }
private:
    boost::recursive_mutex m_mutex;
    list<IObserver *> m_ptrObservers;
    list<IObserver *> m_ptrToAdd;
    int m_nUseCount;
};

class Observer : public IObserver
{
public:
    void OnTrigger(Subject *subject)
    {
        cout << "Observer:OnTrigger" << endl;
        //subject->Detach(this);
        //subject->Atatch(this);
    }
};

int main()
{
    vector<IObserver *> vecObs;
    Subject subject;
    for (int i = 0; i < 10; ++i)
    {
        IObserver *ptrObs = new Observer();
        subject.Atatch(ptrObs);
        vecObs.push_back(ptrObs);
    }

    subject.Trigger();

    {
        // todo delete here 
    }
    return 0;
}

这一次,我们使用了一个UserCount作标记,用来标志当前有几处正在访问观察者列表,如果发现其他处在访问,那么尽量避免直接Add或者Erase一类会使迭代器失效的操作,等到有机会以遍历观察者列表并且只有当前处在访问该列表时,再做真正的增加或删除操作。
另外,还有更加精妙的方案来可以解决我们繁琐的使用UserCount自己进行++,–操作,那就是通过智能指针的机制实现Copy_On_Write-Ref_On_Read,打算再开一篇博文贴代码讲原理。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
观察者模式是一种行为设计模式,用于在对象之间建立一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都能够自动收到通知并进行更新。 以下是观察者模式的实现步骤: 1. 创建主题或被观察者类,该类维护一个观察者列表,并提供方法来注册、删除和通知观察者对象。主题类应该定义一个接口或抽象类,以便观察者能够实现并接收通知。 2. 创建观察者接口或抽象类,该接口或抽象类定义了一个更新方法,当主题发生更改时将被调用。 3. 创建具体的主题类,该类实现主题接口或抽象类,并维护一个状态变量。当状态变量发生更改时,通知所有已注册的观察者,并调用它们的更新方法。 4. 创建具体的观察者类,该类实现观察者接口或抽象类,并在更新方法中执行相应的操作。 以下是一个示例代码: ```java //观察者接口 public interface Observer { void update(int state); } //主题接口 public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(); } //具体的主题类 public class ConcreteSubject implements Subject { private List<Observer> observers = new ArrayList<>(); private int state; public void setState(int state) { this.state = state; notifyObservers(); } public void registerObserver(Observer observer) { observers.add(observer); } public void removeObserver(Observer observer) { observers.remove(observer); } public void notifyObservers() { for (Observer observer : observers) { observer.update(state); } } } //具体的观察者类 public class ConcreteObserver implements Observer { public void update(int state) { System.out.println("State updated to " + state); } } //使用示例 public class Example { public static void main(String[] args) { ConcreteSubject subject = new ConcreteSubject(); ConcreteObserver observer1 = new ConcreteObserver(); ConcreteObserver observer2 = new ConcreteObserver(); subject.registerObserver(observer1); subject.registerObserver(observer2); subject.setState(1); //输出:State updated to 1 subject.removeObserver(observer1); subject.setState(2); //输出:State updated to 2 } } ``` 在上面的示例中,我们首先定义了观察者接口和主题接口。然后,我们创建了具体的主题类`ConcreteSubject`,该类维护一个观察者列表,并实现了主题接口中的方法。当状态变量发生更改时,`ConcreteSubject`会通知所有已注册的观察者。 我们还创建了具体的观察者类`ConcreteObserver`,该类实现观察者接口中的更新方法。在更新方法中,观察者可以执行相应的操作。 最后,我们在`Example`类中创建了一个`ConcreteSubject`对象和两个`ConcreteObserver`对象,并将观察者注册到主题上。当状态变量发生更改时,观察者会自动收到通知并执行相应的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值