Const-correct Notifier in Observer Pattern

∥☆過路亽.° 提交于 2020-01-15 06:39:22

问题


I want to implement an Observer of a Model class which does not change the Model. Thus, it should be able to use a const-Reference to access the Model. But the Registering of the Observer prohibits this.

Here is how the observer pattern is implemented in my Project:



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

};

The only place where a non-modifying observer needs to "change" the Model is when it wants to register with it. I feel that I can not avoid a const_cast here, but I wanted to know if there are better solutions.

Sidenote: Put another way, I don't consider the "List of Observers" which a model object manages to be part of the state of the Model. C++ can not tell the difference and lumps state and observers together, forcing both to be const or non-const.

Cheers, Felix


回答1:


If you consider the Notifier object not to be part of the Model object which owns it, so that modifying the Notifier doesn't "count" as modifying the Model, then make getNotifier a const method returning a non-const reference:

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

     return m_Notifier; 
}

You will then have to either mark m_Notifier as mutable, or else own it by pointer (or smart pointer) or reference rather than by inclusion. Either way, you avoid a const_cast. It is usually preferable to embed objects rather than point/refer to them, but if this is a case where a Notifier isn't considered part of the Model which uses it, then embedding is not essential. Owning it by reference forces you to initialize the reference when Model is constructed, which leads to dependency injection, which is no bad thing. Owning by smart pointer means that, as with embedding, you don't have to do anything about cleanup.

There may be other ways to design things (such as Vinay's addition of another class), but your comment "Is non-const because it needs to return a non-const reference" suggests to me that you can do exactly what you originally wanted, you just don't realise you can.




回答2:


I'm not clear from your code, but if you have a member that is logically const, but physically non-const, the usual solution is to make it mutable.




回答3:


instead

view->SetModel( model ); 

you could call

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



回答4:


An alternative approach to my other answer.

Don't have the observer hold a pointer to the model at all. Pass a const *Model into the update method, which is called by the notifier. This would need to know what model it is notifying for, but that probably isn't difficult given that it's embedded in a model, so it's probably always that one...

If the Observer then needs a non-const Model in SetModel you can still give it one, but more likely is that you'll get rid of SetModel entirely, and just call some_model.AddObserver(some_observer) instead of some_observer.SetModel(some_model).

Similarly but less drastically, you could leave things as they are but declare const *Model m_Model. Then you can use aModel as a non-const Model in SetModel, but no other method of the observer can modify the Model.

Neither of these changes will work if the Observer is expected to be able to unregister itself without a parameter to use to do so.




回答5:


Instead of returning the const Model, you can create one more class which wraps the Notifier object and implements Notifier. (Adapter pattern). Observers can use the newly created class for registering/unregistering.




回答6:


I expect Controller to resolve this:

1.Controller knows Model and allows View registration to 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.Change the MyNonModifingView to accept 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();
    }

};


来源:https://stackoverflow.com/questions/725375/const-correct-notifier-in-observer-pattern

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!