Const-correct Notifier в шаблоне Observer

Я хочу реализовать наблюдатель класса модели, который не меняет модель. Таким образом, он должен иметь возможность использовать 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, но я хотел знать, есть ли лучшие решения.

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

Привет, Феликс


person TheFogger    schedule 07.04.2009    source источник


Ответы (6)


Если вы считаете, что объект Notifier не является частью объекта Model, которому он принадлежит, так что изменение Notifier не «считается» изменением Model, тогда сделайте getNotifier константным методом, возвращающим неконстантную ссылку:

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 другого класса), но ваш комментарий «Неконстантный, потому что он должен возвращать неконстантную ссылку» подсказывает мне, что вы можете делать именно то, что вы изначально хотели, ты просто не понимаешь, что можешь.

person Steve Jessop    schedule 07.04.2009
comment
Я пошел с изменчивым. Раньше я рассматривал возможность внедрения NotificationMediator по разным причинам, но на данный момент это может привести к большим сбоям. - person TheFogger; 08.04.2009

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

person Community    schedule 07.04.2009

вместо

view->SetModel( model ); 

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

model->getNotifier()->addObserver( view );
view->setModel( model ); // this function will accept const Model*
person bayda    schedule 07.04.2009
comment
Мне это очень нравится, но в этом проекте контроллеры интегрированы в представления, и у нас обычно нет их в отдельных классах. - person TheFogger; 08.04.2009

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

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

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

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

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

person Steve Jessop    schedule 07.04.2009

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

person Vinay    schedule 07.04.2009
comment
Я не понимаю, как это будет работать. Конечно, адаптер будет хранить ссылку на адаптер, который должен быть неконстантным. Если бы у меня была такая ссылка, я мог бы использовать ее напрямую. - person TheFogger; 08.04.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();
    }

};
person aJ.    schedule 07.04.2009
comment
Мне это нравится, но наши контроллеры обычно интегрированы в представления. - person TheFogger; 08.04.2009