观察者const正确性

时间:2016-06-09 17:25:05

标签: c++ design-patterns

我正在尝试将观察者模式应用到我的项目中。

想象一下简单的类方法

const Buffer * data() const
{
    if (m_data)
        return m_data;

    // read some data from input
    m_data = fstream.read(1000);

    // subscribe to our buffer
    m_data->Subscribe(this);

    return m_data;
}

此方法用于读取输入数据,但操作可能非常耗时,因此会延迟。

类缓冲区是std :: vector之上的简单包装器,当它的数据被更改时,它会通知观察者。

当Buffer数据发生变化时,需要通知包含类。 但是,由于此方法标记为const,因此无法订阅Buffer。

我能够找到3个解决方案:

1。抛弃常量

    // subscribe to our buffer
    m_data->Subscribe(const_cast<Object*>(this));

我不确定,这是否正确,但是有效。

2. 更改通知方法和观察员的持续性

    vector<const IModifyObserver*> m_observers;
    void Subscribe(const IModifyObserver* observer);
    void Unsubscribe(const IModifyObserver* observer)
    virtual void ObserveeChanged(IModifyObservable*) const override
    {
        m_dirty = true;
    }

这个有一个垮台,如果我需要更改属性,它们都必须是可变的,我调用的所有函数都必须是const,这也没有任何意义。

3. 从各地删除const

    Buffer * data();
    bool Equals(Object& other);
    Buffer* m_data;

这很可能意味着,我必须从整个解决方案中删除const,因为我不能为两个不同的const对象调用Equals。

如何正确解决此问题?

完整代码:

#include <vector>

using namespace std;

class IModifyObservable;

// class for receiving changed notifications
class IModifyObserver
{
public:
    virtual void ObserveeChanged(IModifyObservable* observee) = 0;
    virtual ~IModifyObserver() = 0;
};

// class for producing changed notifications
class IModifyObservable
{
public:
    // Add new subscriber to notify
    void Subscribe(IModifyObserver* observer)
    {
        m_observers.push_back(observer);
    }

    // Remove existing subscriber
    void Unsubscribe(IModifyObserver* observer)
    {
        for (auto it = m_observers.begin(); it != m_observers.end(); ++it) {
            if (observer == *it) {
                m_observers.erase(it);
                break;
            }
        }
    }

    // Notify all subscribers
    virtual void OnChanged()
    {
        auto size = m_observers.size();
        for (decltype(size) i = 0; i < size; ++i) {
            m_observers[i]->ObserveeChanged(this);
        }
    }

    virtual ~IModifyObservable() = 0;

private:
    vector<IModifyObserver*> m_observers;
};

IModifyObserver::~IModifyObserver() {}
IModifyObservable::~IModifyObservable() {}

// Example class implementing IModifyObservable
class Buffer : public IModifyObservable
{
private:
    vector<char> m_data;
};

// Example class implementing IModifyObserver
class Object : public IModifyObserver
{
public:

    // Both share implementation
    //Buffer * data();
    const Buffer * data() const
    {
        // Just read some data
        //m_data = fstream.read(1000);

        // Subscribe to our buffer
        m_data->Subscribe(this);

        return m_data;
    }

    virtual void ObserveeChanged(IModifyObservable*) override
    {
        m_dirty = true;
    }

    // This is just for example, why do I need const data method
    bool Equals(const Object& other) const { return data() == other.data();
}

private:
    mutable Buffer* m_data = new Buffer();
    bool m_dirty;
};

int main()
{
    Object obj1;
    Object obj2;
    auto data1 = obj1.data();
    auto data2 = obj2.data();
    bool equals = (obj1.Equals(obj2));
}

3 个答案:

答案 0 :(得分:1)

这里有什么妨碍你推迟阅读。如果没有这种优化,正确的方法是分离常量和非常量方法:

const Buffer * data() const
{
    return m_data;
}

void InitializeData()
{
    // Just read some data
    m_data = fstream.read(1000);

    // Subscribe to our buffer
    m_data->Subscribe(this);
}

然后按照您想要的方式进行优化:

const Buffer * data() const
{
    if(m_data == nullptr)
        const_cast<Object*>(this)->InitializeData();

    return m_data;
}

你不再需要m_data可变了。

顺便说一句。要使此延迟初始化工作,您应该使用m_data初始化nullptr成员。否则,将在构建时创建此对象,并且if(m_data)将始终为true。

UPD

所以这是你问题的另一种解决方案

class Object : public IModifyObserver
{
public:

    Object()
    : m_data(nullptr)
    , m_dataInitialized(false)
    // ...
    {
        m_data = new Buffer(); // << Create buffer here
        m_data->Subscribe(this); // << And subscribe right away
    }

    const Buffer * data() const
    {
        if(!m_dataInitialized) // << Initialize if needed
        {
            // Set data here
            m_data->setData(fstream.read(1000)); // << Probably you want to suppress notifications here
            m_dataInitialized = true;
        }
        return m_data;
    }
    // ...
private:
    mutable Buffer* m_data;
    mutable bool m_dataInitialized; // << Added another flag to see if data was initialized
    // ...
};

答案 1 :(得分:1)

我冒昧地重构了你的代码,我无法看到你的例子中对data()的初始调用会发生在哪里,但我想它是以两阶段方式调用的(构造 - &gt;然后调用方法)。坚持简单的规则..

#include <algorithm>
#include <memory>
#include <vector>

using namespace std;

class IModifyObservable;

// class for receiving changed notifications
class IModifyObserver
{
public:
    virtual void ObserveeChanged(IModifyObservable* observee) = 0;
    virtual ~IModifyObserver() = default;
};

// class for producing changed notifications
class IModifyObservable
{
public:
    // This method modifies state - so non-const
    void Subscribe(IModifyObserver* observer)
    {
        observers_.push_back(observer);
    }

    // This method modifies state - so non-const
    void Unsubscribe(IModifyObserver* observer)
    {
        observers_.erase(find(begin(observers_), end(observers_), observer));
    }

    // Again as the internal state of the observer is modified, this is non-const
    virtual void OnChanged()
    {
        for (auto observer : observers_) {
            observer->ObserveeChanged(this);
        }
    }

    virtual ~IModifyObservable() = default;

private:
    vector<IModifyObserver*> observers_;
};

// Example class implementing IModifyObservable
class Buffer : public IModifyObservable
{
    vector<char> data_;
};

// Example class implementing IModifyObserver
class Object : public IModifyObserver
{
public:

    // The first call to the non-cost version triggers the lazy load...
    const Buffer* data()
    {
        if (!data_) {
            data_ = make_unique<Buffer>();
            // Now start the read operation
            // :
            // Subscribe, I imagine you only want to do this once?
            data_->Subscribe(this);
        }
        return data_.get();
    }

    // Calls to const version returns what's there...
    const Buffer* data() const
    {        
        return data_.get();
    }

    // This has to be non-cost as the internal state is being modified
    void ObserveeChanged(IModifyObservable*) override
    {
        dirty_ = true;
    }

    // Operator uses const versions, which will use const methods
    friend
    bool operator==(const Object& lhs, const Object& rhs) {
        if (lhs.data() && rhs.data()) {
        }
        return false;
    }

private:
    unique_ptr<Buffer> data_;
    bool dirty_ = false;
};

int main()
{
    Object obj1;
    Object obj2;
    auto data1 = obj1.data();
    auto data2 = obj2.data();
    bool equals = obj1 == obj2;
}

没有黑客,它应该有用......

答案 2 :(得分:0)

避免在getter中注册,在初始化中注册:

class Object : public IModifyObserver
{
public:
    Object() { m_data.Subscribe(this); }

    const Buffer* data() const { return m_data; }
    Buffer* data() { return m_data; }

    void ObserveeChanged(IModifyObservable*) override { m_dirty = true; }
private:
    Buffer m_data;
    bool m_dirty = false;
};

使用延迟初始化,它变为:

class Object : public IModifyObserver
{
public:
    Object() { m_data.Subscribe(this); }

    Buffer& data()
    {
        if (!m_data.is_initialized()) { m_data.initialize(); }
        return m_data;
    }
    const Buffer& data() const
    {
        if (!m_data.is_initialized()) { m_data.initialize(); }
        return m_data;
    }

    void ObserveeChanged(IModifyObservable*) override { m_dirty = true; }

private:
    mutable Buffer m_data;
    bool m_dirty = false;
};

Demo

相关问题