jeudi 9 juin 2016

Observer const-correctness

I am trying to implement observer pattern into my project.

Imagine simple a class method

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;
}

This method is used to read input data, but the operation could be time consuming, it is therefore delayed.

Class Buffer is simple wrapper above std::vector, which notifies observers, when it's data being altered.

The containing class needs to be notified, when Buffer data changes. However, since this method is marked as const, I am unable to subscribe to the Buffer.

I was able to figure out 3 solutions:

1. Cast away const-ness

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

I am not sure, whether this is correct, but it works.

2. Change const-ness of notification method and observers

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

This one has a downfall, if I need to change properties they all have to be mutable and all functions I call must be const, which also does not make any sense.

3. Remove const from everywhere

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

This would most probably mean, that I would have to remove const from whole solution, since I can't event call Equals for two different const objects.

How to properly solve this problem?

Full Code:

#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));
}

Aucun commentaire:

Enregistrer un commentaire