2016-06-09 104 views
1

我想實現觀察者模式到我的項目中。觀察者const正確性

想象簡單類方法

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之上的簡單包裝,它在數據被更改時通知觀察者。

當緩衝區數據發生變化時,需要通知包含類。 但是,由於此方法被標記爲const,我無法訂閱緩衝區。

我能想出3個解決方案:

遠離鑄造常量性

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

這其中有一個倒臺,如果我需要改變,他們都必須是可變的特性,所有的功能我調用必須是常量,這也沒有任何意義。來自世界各地的

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

刪除常量這很可能意味着,我必須從整體解決方案中刪除常量,因爲我不能事件調用的Equals兩個不同的const對象。

如何正確解決這個問題?

全碼:

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

我不是常規專家,所以我沒有發佈答案,但我會做選項1:確保你的'ObserveeChanged()'實際上不修改'this',然後拋棄'const'來實現接口。 – rodrigo

+0

'm_data-> Subscribe(this);'不應該在getter中完成。另外,'m_data = fstream.read(1000)''已經打破了'const'部分。 – Jarod42

+0

m_data被標記爲可變。這在每種緩存方法中都是常見的做法。應該在哪裏「m_data->訂閱(this);」叫做? – Gotcha

回答

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到了可變的。


BTW。要使這個延遲初始化工作,你應該初始化m_data成員nullptr。否則,該對象將在構建時創建,並且您的if(m_data)將始終爲真。


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 
    // ... 
}; 
+0

此解決方案看起來幾乎像列表中的數字1。然而'const_cast (this)'對我來說似乎很骯髒。我也相信,如果你試圖拋棄一個常量對象的常量並修改它,那麼這個行爲是不確定的。 – Gotcha

+0

@Gotcha''const_cast'是安全的,當被鑄造的對象最初是非常量的。所以你相信未定義的行爲取決於你如何使用這段代碼。爲了避免這種轉換,你可以在'data()'返回'nullptr'時手動調用'InitializeData()'。 – teivaz

+0

如果你明白,我想解決這個問題,而不需要任何關於調用者的假設。當數據返回null方法正在工作時調用初始化的解決方案,但我也不喜歡它。 – Gotcha

1

我把重構你的代碼的自由,我看不出哪裏data()初始調用會在發生你的例子,但我想它是以2階段的方式調用(構造 - >然後調用方法)。用簡單的規則堅持..

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

有沒有黑客,它應該只是工作...

+0

整個問題是關於'const Buffer * data()const'方法,你改變了,它不再讀取輸入數據。這意味着,我必須調用非const方法來初始化某處的數據,但這正是我想要避免的。這整個類基本上是關於懶加載緩衝區數據 – Gotcha

+0

有問題,如果你期望對象的內部被延遲加載,你應該傳播一個非const的實例。否則急切加載並傳播一個const實例。混合語義只會在晚些時候讓你陷入麻煩......你最終會擁有const函數和可變成員......坦率地說是可怕的...... – Nim

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