Состояния объектов
От: ZiloT  
Дата: 27.12.07 19:39
Оценка:
Значит так.
Очень часто возникает следующая проблема:
Есть некий диалог (1) для создания объекта OBJECT_A. Внутри этого диалога можно вызвать ещё один диалог (2), который в свою очередь создает объект типа OBJECT_B. Но объект типа OBJECT_B зависит от пока ещё не созданного объекта типа OBJECT_B. Такая цепочка и взаимосвязь может быть большой...
Возникает проблема, как, например, работать диалогу (2), если данные, на которых он основан (диалог 1), ещё не созданы.

Для этого был написан классик, "Состояние":


class IDialogState
{
public:
    
    virtual ~IDialogState();
    
    void Save(void* pData);

    IDialogState* Copy();
    
    bool AddState(IDialogState* pState);
    bool ReplaceState(IDialogState* pOldState, IDialogState* pNewState);
    IDialogState* FindState(DWORD dwId) const;
    bool DeleteState(DWORD dwId);
    
    DWORD GetId() const;
    void SetId(DWORD dwId);
    
protected:

    IDialogState();

    IDialogState* Clone();
    
    virtual void* DoSave(void*) = 0;

    virtual IDialogState* DoClone() = 0;

    virtual void DoCopy(IDialogState* pCopyState) = 0;
    
    typedef std::vector<IDialogState*>        TStates;
    typedef TStates::iterator            TStatesIt;
    typedef TStates::const_iterator            TStatesConstIt;
    
    TStates m_States;
    
    DWORD    m_dwId;
};


Класс описывает состояние диалога. Да к тому же содержит указатели на зависимые от него состояния. Так же каждое состояние имеет уникальное обозначение в рамках всей этой могучей иерархии состояний.

Далее. Был написан класс IBaseDialog, описывающий работу с диалогом и с его состоянием.
Любой производный от базового диалога должен иметь свое состояние, производное от базового.
Так же диалог имеет указатель на свой родительский диалог, если он был вызван из другого диалога.


/** Базовый класс для диалогов
*/
class IBaseDialog : public CDialog
{
public:

    virtual ~IBaseDialog() {}

    UINT GetResourceId() const;

    /** Диалог всегда работает с каким-то данным,
    вот это данное и должна возвращать эта функция...
    */
    virtual void* GetData() { return NULL; }

    /** Получить состояние диалога.
    */
    IDialogState* GetState() const;
    
    /** Добавить состояние в качестве дочернего текущему.
    */
    bool AddState(IDialogState* pState);

    IDialogState* FindState(DWORD dwId) const;

    bool ReplaceState(IDialogState* pOldState, IDialogState* pNewState);

    void SetUniqueId(DWORD dwId);

    // создать ГУИ
    virtual void CreateGUI();

    /** Обновить ГУИ из состояния.
    */
    virtual void UpdateGUIFromState()    {};

    /** Обновить состояние из данного, с которым работает диалог.
    */
    virtual void UpdateStateFromData()    {}

    /** Обновить состояние из ГУИ.
    */
    virtual void UpdateStateFromGUI()    {};

    // Проверка введенных данных в диалог.
    virtual bool VerifyUserInput() { return true; }

    virtual BOOL OnInitDialog();

    virtual void OnOK();

    virtual void OnCancel();

    virtual BOOL DoInit();

protected:

    IBaseDialog(UINT iResourceId, IBaseDialog* pParentDialog);

    UINT            m_uResourceId;

    IBaseDialog*    m_pParentDialog; // родительский диалог
    IDialogState*    m_pState;        // текущее состояние диалога
    IDialogState*    m_pSrcState;     // копия состояния

    DWORD            m_dwExternalUniqueId;

    bool            m_bDoUserInputVerification;

        IDialogState* CreateState();

    virtual IDialogState*    CreateStatePtr() { return NULL; }

    virtual DWORD GetUniqueId();

    virtual bool DoOK();

    virtual void DoCancel();

    virtual void DoDataExchange(CDataExchange* pDX);
};



Есть функция DoInit, которая вызывается при инициализации диалога.

BOOL IBaseDialog::DoInit()
{
    bool bRestored = false;
    IDialogState* pState = NULL;

    CreateGUI();

    // запросим у родителя, если он есть свое состояние...
    if (m_pParentDialog)
    {
        pState = m_pParentDialog->FindState(GetUniqueId());
        if (pState)
            bRestored = true;
    }

    // если нет состояния, то создадим его...
    if (pState == NULL)
        pState = CreateState();

    if (bRestored)
    {
        m_pSrcState = pState;
        if (pState)
            m_pState = pState->Copy();
    } 
    else // если было только что создано, то копию нет смысла делать...
    {
        m_pState = pState;
        UpdateStateFromData(); // обновим состояние из данного, с которым работает диалог
    }

    UpdateGUIFromState(); // обновим ГУИ из состояния

    return TRUE;
}


Есть функция DoOK, которая вызывается при нажатии OK в диалоге.

bool IBaseDialog::DoOK()
{
    if (m_bDoUserInputVerification && !VerifyUserInput())
        return false;

    UpdateStateFromGUI();

    if (m_pParentDialog)
    {
        if (m_pSrcState)
        {
            /*    если не NULL, значит делали копию, и в этой переменной хранится
            исходное состояние, которое как раз и надо удалить
            а в родительский диалог на его место перезаписать ранее созданную копию
            */

            m_pParentDialog->ReplaceState(m_pSrcState, m_pState);
            delete m_pSrcState;
        }
        else // иначе... отдадим родителю на хранение свое состояние :)
            m_pParentDialog->AddState(m_pState);
    }
    else // сам является родителем
    {
        // тогда сохранимся
        if (m_pState)
            m_pState->Save(GetData());

        // удалим состояние
        delete m_pState;
    }

    return true;
}


По нажатию кнопки "Отмена" состояние просто грохается.

Вопрос такой, как избавиться от void* в выделенных местах???
Не могу понять, как сюда прикрутить шаблоны...

Да и вообще, может можно как-то описывать на этапе компиляции взаимосвязь всех диалогов друг с другом, что от чего зависит и т.п.
по аналогии с конечными автоматами из boost.
Re: Состояния объектов
От: Sashaka Россия  
Дата: 27.12.07 20:26
Оценка:
отдели объекты от диалогов, сделай над ними удобный интерфейс, а из диалогов только вызывай его методы, жизнь станет легче, уверяю.

Что-бы этот интерфейс был доступен из всех диалогов, используй паттерн Singleton.

Посмотри также паттерн Composite, по моему это то, что тебе нужно.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.