Const-правильный уведомитель в шаблоне наблюдателя - PullRequest
2 голосов
/ 07 апреля 2009

Я хочу реализовать Observer класса Model, который не меняет модель. Таким образом, он должен иметь возможность использовать const-Reference для доступа к модели. Но регистрация наблюдателя запрещает это.

Вот как шаблон наблюдателя реализован в моем проекте:



//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 здесь, но я хотел знать, есть ли лучшие решения.

Sidenote: Иными словами, я не рассматриваю «Список наблюдателей», который объект модели управляет как часть состояния Модели. C ++ не может различить разницу и объединяет состояние и наблюдателей вместе, заставляя оба быть постоянными или неконстантными.

Ура, Феликс

Ответы [ 6 ]

5 голосов
/ 07 апреля 2009

Если вы считаете, что объект Notifier не является частью объекта Model, которому он принадлежит, так что изменение Notifier не «считается» как изменение Model, тогда сделайте getNotifier 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. Обычно предпочтительнее встраивать объекты, чем указывать / ссылаться на них, но если это тот случай, когда средство оповещения не считается частью модели, которая его использует, то встраивание не является обязательным. Владение им по ссылке заставляет вас инициализировать ссылку при построении модели, что приводит к внедрению зависимости, что не так уж и плохо. Владение умным указателем означает, что, как и при встраивании, вам не нужно ничего делать для очистки.

Могут быть и другие способы разработки вещей (например, добавление Vinay другого класса), но ваш комментарий «Неконстантный, потому что он должен возвращать неконстантную ссылку», подсказывает мне, что вы можете делать именно то, что изначально хотел, ты просто не понимаешь, что можешь.

3 голосов
/ 07 апреля 2009

Мне не понятно из вашего кода, но если у вас есть член, который является логически постоянным, но физически неконстантным, обычное решение - сделать его изменяемым .

1 голос
/ 07 апреля 2009

Альтернативный подход к моему другому ответу.

Не заставляйте наблюдателя вообще держать указатель на модель. Передайте const *Model в метод обновления, который вызывается уведомителем. Это должно было бы знать, какую модель он уведомляет, но это, вероятно, не сложно, учитывая, что она встроена в модель, так что, вероятно, она всегда такая ...

Если Обозревателю тогда нужна неконстантная Модель в SetModel, вы все равно можете дать ее, но более вероятно, что вы полностью избавитесь от SetModel и просто вызовете some_model.AddObserver(some_observer) вместо some_observer.SetModel(some_model).

Точно так же, но менее радикально, вы можете оставить вещи такими, какие они есть, но объявить const *Model m_Model. Затем вы можете использовать aModel как неконстантную модель в SetModel, но никакой другой метод наблюдателя не может изменить модель.

Ни одно из этих изменений не сработает, если ожидается, что Наблюдатель сможет отменить свою регистрацию без параметра, который можно использовать для этого.

1 голос
/ 07 апреля 2009

вместо

view->SetModel( model ); 

вы могли бы позвонить

model->getNotifier()->addObserver( view );
view->setModel( model ); // this function will accept const Model*
0 голосов
/ 07 апреля 2009

Я ожидаю, что контроллер разрешит это:

1.Контроллер знает модель и позволяет просматривать регистрацию модели.

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

};
0 голосов
/ 07 апреля 2009

Вместо возврата const Model вы можете создать еще один класс, который оборачивает объект Notifier и реализует Notifier. (Шаблон адаптера). Наблюдатели могут использовать вновь созданный класс для регистрации / отмены регистрации.

...