观察者模式中的正确通知程序

时间:2009-04-07 12:13:33

标签: c++ const observer-pattern const-correctness

我想实现一个Model类的Observer,它不会改变Model。因此,它应该能够使用const-Reference来访问Model。但注册观察员禁止这样做。

以下是我的项目中实现观察者模式的方式:



//Attributes of type Observable are used by classes that want to notify others
//of state changes. Observing Objects register themselves with AddObserver.
//The Observable Object calls NotifyObservers when necessary.
class Notifier
{
public:
    AddObserver(Observer*);
    RemoveObserver(Observer*);
    NotifyObservers();
};

class Model
{
public:
    Notifier& GetNotifier() //Is non const because it needs to return a non-const 
    {                       //reference to allow Observers to register themselves.

         return m_Notifier; 
    }

    int QueryState() const;

    void ChangeModel(int newState)
    {
        m_Notifier.NotifyObservers();
    }

private:
    Notifier m_Notifier;
};

//This View does not Modify the Model.
class MyNonModifingView : public Observer
{
public:
    SetModel(Model* aModel)  //should be const Model* aModel...
    {
        m_Model = aModel;
        m_Model->GetNotifier().AddObserver(this); //...but can't because
        //SetModel needs to call GetNotifier and add itself, which requires
        //non-const AddObserver and GetNotifier methods.
    }

    void Update()  //Part of Observer-Interface, called by Notifiers
    {
        m_Model->QueryState();
    }

};

非修改观察者需要“更改”模型的唯一地方是它想要注册它的时间。我觉得我无法在这里避免使用const_cast,但我想知道是否有更好的解决方案。

旁注: 换句话说,我不认为模型对象设法成为模型状态的一部分的“观察者列表”。 C ++无法区分并将状态和观察者结合在一起,迫使它们都是const或非const。

干杯,菲利克斯

6 个答案:

答案 0 :(得分:5)

如果您认为Notifier对象不属于拥有它的Model对象的一部分,那么修改Notifier不会“计数”为修改Model,那么使getNotifier成为一个返回非const引用的const方法:

Notifier& GetNotifier() const //Is const but returns a non-const 
{                             //reference to allow Observers to 
                              //register themselves.

     return m_Notifier; 
}

然后,您必须将m_Notifier标记为可变,或者通过指针(或智能指针)或引用而不是包含来拥有它。无论哪种方式,您都避免使用const_cast。通常最好嵌入对象而不是指向/引用它们,但如果这是Notifier不被认为是使用它的模型的一部分的情况,则嵌入不是必需的。通过引用拥有它会强制您在构造Model时初始化引用,这会导致依赖注入,这不是坏事。拥有智能指针意味着,与嵌入一样,您不必对清理做任何事情。

可能还有其他设计方法(例如Vinay添加了另一个类),但是你的评论“非const是因为它需要返回一个非const引用”,这表明你可以完全按照你的意思去做最初想要的,你只是没有意识到你可以。

答案 1 :(得分:3)

我不清楚你的代码,但是如果你有一个逻辑上是const的成员,但是物理上是非const的,那么通常的解决办法就是让它可变

答案 2 :(得分:1)

而不是

view->SetModel( model ); 

你可以打电话

model->getNotifier()->addObserver( view );
view->setModel( model ); // this function will accept const Model*

答案 3 :(得分:1)

另一种解决方法。

根本没有观察者手指指向模型的指针。将const *Model传递给更新方法,该方法由通知程序调用。这需要知道它通知的是什么模型,但这可能并不困难,因为它嵌入在模型中,所以它可能总是那个......

如果Observer在SetModel中需要一个非const模型,你仍然可以给它一个,但更有可能的是你将完全摆脱SetModel,只需调用some_model.AddObserver(some_observer)而不是{{1} }。

同样但不那么剧烈,你可以保留原样,但声明some_observer.SetModel(some_model)。然后你可以在SetModel中使用aModel作为非const模型,但观察者的其他方法都不能修改模型。

如果希望Observer能够在没有用于执行此操作的参数的情况下取消注册,则这些更改都不会起作用。

答案 4 :(得分:0)

您可以创建另一个包装Notifier对象并实现Notifier的类,而不是返回const Model。 (适配器模式)。观察者可以使用新创建的类进行注册/取消注册。

答案 5 :(得分:0)

我希望Controller解决这个问题:

1.Controller知道Model并允许View注册到Model。

class MyController 
{
public:

    //Controller associated with the Model
    MyController(Model* pModel):m_pModel(pModel)
    {
    }

    //Provide the facility to register the view. 
    //Alternatively, if there is 1:1 relation between controller and View then View poniter can be stored locally inside Controller
    void registerObserver(Observer* pObserver) 
    {
               //Register observer  
        m_pModel->GetNotifier().AddObserver(pObserver);
               //set the model in view
        pObserver->SetModel(m_pModel);

    }
};

2.更改MyNonModifingView以接受const Model * aModel

class MyNonModifingView : public Observer
{
public:
    SetModel(const Model* aModel) 
    {
        m_Model = aModel;
    //NO need to register here, My controller does it for me.
        //   m_Model->GetNotifier().AddObserver(this);
    }

    void Update()  //Part of Observer-Interface, called by Notifiers
    {
        m_Model->QueryState();
    }

};