Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

MVC в c++.

87
06 декабря 2008 года
Kogrom
2.7K / / 02.02.2008
Читал про MVC (Model-view-controller), смотрел на диаграммы - понимается с трудом. Попробовал реализовать короткий пример на си++. Но не уверен, что правильно сделал. Может кто-нибудь проверить? Может логика работы неправильна?

Код:
#include <iostream>

// Интерфейсы (чтобы хедер модели был
//   не нужен контроллеру и виду)
struct mParentC
{
    virtual void SetParam(int a) = 0;
};

struct mParentV
{
    virtual int GetParam() = 0;
};

// Модель
class m: public mParentC, public mParentV
{
    int param;
public:
    int GetParam(){return param;}
    void SetParam(int a){param = a;}

    m(int a){ param = 0;}
};

// Вид
struct v
{
    mParentV *mPtr;
    v(mParentV *p):mPtr(p){}
    void PrintParam()
    {
        std::cout << mPtr->GetParam() << std::endl;
    }
    static void PrintError()
    {
        std::cout << "Error" << std::endl;
    }
};

// Контроллер
struct c
{
    mParentC *mPtr;
    c(mParentC *p):mPtr(p){}
    void SetParam(int a)
    {
        if(a > 0)
            mPtr->SetParam(a);
        else
            v::PrintError();
    }
};

int main()
{
    m mObject(5);
    c cObject(&mObject);
    v vObject(&mObject);

    vObject.PrintParam();
    cObject.SetParam(-1);
    cObject.SetParam(10);
    vObject.PrintParam();
    return 0;
}
341
07 декабря 2008 года
Der Meister
874 / / 21.12.2007
У вас наоборот как-то получается: модель зависит как от контроллера, так и от представления. Здесь как бы композиция пригодится и агрегация (частный случай асоциации) даст вам необходимую абстракцию (это не рэп, это хип-хоп :)):
 
Код:
class Controller
{
private:
    Model m_Model;
    View * m_pView; // или даже Views
};
либо
 
Код:
class Controller : public View // или public IController, public View
{
    Model * m_pModel;
};
либо и то и другое и можно без хлеба. В С++ я старался не пользоваться агрегацией (видимое проявление - указатель на другой объект с внешним Get/Set) из-за сложности контроля над временем жизни агрегируемого объекта (здесь необходим как минимум смартпоинтер или фабрика + release() по типу COM). Одной композицией, конечно, обойтись можно редко, зато можно строить комбинации:
Код:
class Controller
{
private:
    Model m_Model;

    class Subcontroller : public View
    {
    private:
        Model * m_Model;
    };

    Subcontroller * m_pSubcontroller; // = new Subcontroller(&m_Model);
}
В MFC-концепции "документ/вид" документ является примером такой схемы (хотя представления там доступны для агрегации, то есть производные от View классы видимы извне и Document раздаёт на них указатели, у Document монополия на создание/удаление представлений: экземпляр документа по умолчанию един и глобален).
341
07 декабря 2008 года
Der Meister
874 / / 21.12.2007
Ах, да. Если простительно синхронизировать модель и представление в ходе единой операции, возможен простой частный случай:
Код:
template <typename T>
struct ISimpleController
{
    virtual void StroreModel(T * model) = 0;
    virtual void RestoreModel(T * model) = 0;
};

class Controller : public ISimpleController<Model>, public View
{
    void StroreModel(Model * model)
    {
    }

    void RestoreModel(Model * model)
    {
    }
};
Однако, со временем вы убедитесь, что агрегация делает процесс гибче.
87
07 декабря 2008 года
Kogrom
2.7K / / 02.02.2008
Цитата: Der Meister
У вас наоборот как-то получается: модель зависит как от контроллера, так и от представления.


Может я чего-то не понял, но в моем примере при изменении Контроллера или Представления Модель не меняется - в ней же не используется объекты этих классов. Или говорится о другой зависимости?

Цитата: Der Meister
В С++ я старался не пользоваться агрегацией (видимое проявление - указатель на другой объект с внешним Get/Set) из-за сложности контроля над временем жизни агрегируемого объекта (здесь необходим как минимум смартпоинтер или фабрика + release() по типу COM).


Ну, для контроля времени жизни можно код поменять:

Код:
// Интерфейс модели
struct ModelInterface
{
    virtual void SetParam(int a) = 0;
    virtual int GetParam() = 0;
    ~ModelInterface(){}
};

class Model: public ModelInterface
{
    int param;
public:
    int GetParam(){return param;}
    void SetParam(int a){param = a;}

    Model(int a){ param = a;}
};

struct View
{
    static void PrintParam(ModelInterface *mPtr)
    {
        std::cout << mPtr->GetParam() << std::endl;
    }
    static void PrintError()
    {
        std::cout << "Error" << std::endl;
    }
};

class Controller
{
    ModelInterface *m_pModel;
public:
    Controller()
    {
        m_pModel = new Model(5);
    }
    ~Controller()
    {
        delete m_pModel;
    }
    void SetParam(int a)
    {
        if(a > 0)
            m_pModel->SetParam(a);
        else
            View::PrintError();
    }
    void PrintParam()
    {
        View::PrintParam(m_pModel);
    }
};

int main()
{
    Controller m_Controller;

    m_Controller.PrintParam();
    m_Controller.SetParam(3);
    m_Controller.PrintParam();
    m_Controller.SetParam(-1);

    return 0;
}
341
07 декабря 2008 года
Der Meister
874 / / 21.12.2007
[QUOTE=Kogrom]Может я чего-то не понял, но в моем примере при изменении Контроллера или Представления Модель не меняется - в ней же не используется объекты этих классов. Или говорится о другой зависимости?[/QUOTE]Модель не должна затачиваться под определённую систему, она должна иметь ту архитектуру, которую ей необходимо иметь. Если вам необходимо будет изменить набор отображаемых данных (изменятся mParentC, mParentV или ваш новый ModelInterface) изменится и Model. Если имеется готовый, написанный не вами, либо вами, но для иной задачи, класс, который вы захотите использовать в качестве модели, вам придётся заточить его под ваш ModelInterface. В идеале, MVC позволяет использовать как модель, так и представление, взятые из любых источников, "как есть".
87
07 декабря 2008 года
Kogrom
2.7K / / 02.02.2008
Цитата: Der Meister
Модель не должна затачиваться под определённую систему, она должна иметь ту архитектуру, которую ей необходимо иметь. Если вам необходимо будет изменить набор отображаемых данных (изменятся mParentC, mParentV или ваш новый ModelInterface) изменится и Model. Если имеется готовый, написанный не вами, либо вами, но для иной задачи, класс, который вы захотите использовать в качестве модели, вам придётся заточить его под ваш ModelInterface. В идеале, MVC позволяет использовать как модель, так и представление, взятые из любых источников, "как есть".


Вообще-то ModelInterface - это и есть часть компонента модели. То есть я вынес в него все функции модели, которые могут потребоваться каким-либо другим классам. В нем могут быть функции, которые не используются Контроллером и Представлением.

Он не есть обязательная часть. Можно обращаться напрямую к модели, но если обращаться через такой "интерфейс", то не надо включать в Контроллер хедер всей Модели, который может содержать кучу ненужных для других классов данных.

По идее, в готовом чужом классе, должно быть что-то подобное: то есть набор нужных функций, через которые можно получить доступ ко всем нужным данным.

341
07 декабря 2008 года
Der Meister
874 / / 21.12.2007
Ох! Смутили меня mParentV и mParentC. Главное-то, главное...
Пройдёмся по ответственностям: View - отображение, Model - часть предметной области, Controller - высокоуровневые операции. Пример операции для Controller в вашем случае: запросить значение через View, вычислить результат через Model, вывести результат через View. Всё это - одна операция. Другой пример: запросить данные, запросить имя файла, передать данные вычисляющей модели, получить результат, отдать его и имя файла модели, реализующей сохранение данных, сообщить о результате. Опять одна операция.
Композиция MVC: есть таблица с данными, необходимо её сохранить. Модель трёхуровневая. Первый - общий, второй - для таблицы (набора записей в контексте модели), третий - для строки таблицы (записи). Контроллеры всех трёх уровней имеют высокоуровневую операцию SaveData(). Контроллер первого уровня запрашивает имя файла, делегирует вызов контроллеру второго уровня. Контроллер второго уровня проходит по всем записям, и делегирует вызов контроллеру для каждой из них (условно. контроллер может быть один и иметь метод SaveData(TableRow, Record), либо являться наследником TableRow, если класс таблицы позволяет это сделать). Контроллер третьего уровня синхронизирует строку таблицы (вид) с записью (модель) и возвращает управление. Контроллер первого уровня передаёт модели путь к файлу и вызывает её метод SaveToFile(). В контексте контроллера первого уровня, всё это - одна операция.
87
08 декабря 2008 года
Kogrom
2.7K / / 02.02.2008
Цитата: Der Meister
Другой пример: запросить данные, запросить имя файла, передать данные вычисляющей модели, получить результат, отдать его и имя файла модели, реализующей сохранение данных, сообщить о результате. Опять одна операция.


Поупражняюсь для лучшего понимания. Работу с файлами заменю на что-нибудь.

Код:
class ModelCalc
{
    int param;
public:
    int GetResult(){return param * 2;}
    ModelCalc(int a){ param = a;}
};

namespace ModelSave
{
    bool SaveNumberToFile(std::string fileName, int n)
    {
        if(n) return true;
        else return false;
    }
};

namespace View
{
    std::string GetFileName()
    {
        std::cout << "Input File Name: ";
        std::string fileName;
        std::cin >> fileName;
        return fileName;
    }
    int GetNumber()
    {
        std::cout << "Input Number: ";
        int n = 0;
        if(!(std::cin >> n))
        {
            std::cin.clear();
            std::cin.ignore(255, '\n');
            std::cout << "Error!" << std::endl;
            return GetNumber();
        }
        return n;
    }
    void GoodSave()
    {
            std::cout << "File Saved" << std::endl;
    }
};

struct Controller
{
    bool InputAndSave()
    {
        ModelCalc mCalc(View::GetNumber());
        if(ModelSave::SaveNumberToFile(View::GetFileName(), mCalc.GetResult()))
        {
            View::GoodSave();
            return true;
        }
        return false;
    }
};

int main()
{
    Controller m_Controller;

    while(m_Controller.InputAndSave()){}

    return 0;
}



Цитата: Der Meister
Композиция MVC: есть таблица с данными, необходимо её сохранить. Модель трёхуровневая. Первый - общий, второй - для таблицы (набора записей в контексте модели), третий - для строки таблицы (записи). Контроллеры всех трёх уровней имеют высокоуровневую операцию SaveData(). Контроллер первого уровня запрашивает имя файла, делегирует вызов контроллеру второго уровня. Контроллер второго уровня проходит по всем записям, и делегирует вызов контроллеру для каждой из них (условно. контроллер может быть один и иметь метод SaveData(TableRow, Record), либо являться наследником TableRow, если класс таблицы позволяет это сделать). Контроллер третьего уровня синхронизирует строку таблицы (вид) с записью (модель) и возвращает управление. Контроллер первого уровня передаёт модели путь к файлу и вызывает её метод SaveToFile(). В контексте контроллера первого уровня, всё это - одна операция.


Интереснее было бы, если бы таблица, не сохранялась, а загружалась. Тогда и нужно синхронизировать с видом.
Итак. Модель первого уровня - самодельный класс, второго уровня - вектор строк, третьего - строка. Контроллер первого уровня получает через Вид имя файла и передает модели. Модель загружает данные из файла (хотя это мог бы наверно делать один из контроллеров). После этого Контроллер второго уровня синхронизирует таблицу с видом.

Код:
using namespace std;

class Model
{
    vector<string> table;
public:
    Model():table(10, "line"){}

    size_t Size(){return table.size();}

    string GetLine(size_t n){ return table[n]; }

    void Load(string fileName)
    {
        for(size_t i = 0; i < table.size(); ++i)
        {
            table = fileName;
        }
    }
};

namespace View
{
    string GetString(string text)
    {
        cout << "Input "<< text <<": ";
        string str;
        cin >> str;
        return str;
    }
    void PrintStr(const string &line)
    {
        cout << line << endl;
    }
};

class Controller
{
    Model m_Model;

    struct SubController
    {
        Model *m_pModel;
        SubController(Model *p):m_pModel(p){}
        void UpdateTable()
        {
            for(size_t i = 0; i < m_pModel->Size(); ++i)
            {
                View::PrintStr(m_pModel->GetLine(i));
            }
        }
    };

public:

    void LoadFromFile()
    {
        m_Model.Load(View::GetString("File Name"));
        SubController sc(&m_Model);
        sc.UpdateTable();
    }
};

int main()
{
    Controller m_Controller;

    m_Controller.LoadFromFile();

    return 0;
}


Вроде принцип понял. Осталось проникнуться смыслом всего этого :)
341
09 декабря 2008 года
Der Meister
874 / / 21.12.2007
[QUOTE=Kogrom]Вроде принцип понял. Осталось проникнуться смыслом всего этого[/QUOTE]Вообще говоря, View - не обязательно пользовательский интерфейс. Это может быть интерфейс к любым данным "извне". Например, MVC работает в ADO .NET: есть база данных, есть объект DataTable, есть TableAdapter. DataTable не привязана к базе данных и существует отдельно он неё. Такая схема избавляет приложение от привязки к конкретной СУБД + даёт возможность работать с автономной копией данных, что экономит канал связи. В этой системе объект DataTable является моделью. Контроллером уровня таблицы в БД является TableAdapter, выполняющий синхронизацию. Отображение, с которым он работает - SQL-запрос и его результаты. В то же самое время, данные могут быть синхронизированы с элементом управления пользовательского интерфейса DataGridView (объект-модель DataTable - тот же), а контроллером здесь служит объект BindingSource, выполняющий необходимую синхронизацию. Более того, элемент записи может быть автоматически синхронизирован при помощи контроллера с текстовым полем, например.
Следует заметить, что в чистом виде вторая часть взаимодействия работает автоматически благодаря механизму событий .NET. В С++, к сожалению, для подобной автоматики нередко приходится делать дополнительную декомпозицию, и делать подконтроллер частью отображения (либо придётся долго выяснять, какие записи были удалены, какие - добавлены, а какие - изменены).
И самое главное. Синхронизация - лишь крохотная часть задач, возлагаемых на контроллер. Вообще, повторюсь, контроллер производит высокоуровневые операции над моделью и отображением.
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог