Как стать автором
Обновить

Реализация MVC на C++

Паттерн MVC является одним из архитектурных паттернов, предназначением которого является разделение логики, представления и управления.

Данный шаблон проектирования, на самом деле довольно гибкий и интересный, как и его способы реализации. В каком-то случае можно усилить ответственность Представления, в другом, можно увеличить ответственность Контроллера и т.д, но основная логика всегда остается одинаковой:
Модель выполняет роль стуктуры информация, которую можно будет изменять через Контроллер и визуализировать при помощи Представления.

Я попытаюсь привести пример реализации MVC с минимальной зависимостью Представления от Модели. Также, в моей реализации логика работы определяется контроллером (т.е. как раз таки в этом случае будет повышена его ответственность, что приведет к большей гибкости).

Итак, для реализации MVC нам понадобятся еще 2 паттерна, таких как Observer/Наблюдатель и Command/Команда.

В простейшей реализации будет достаточно, чтобы Модель просто наследовала класс Observable для оповещения Представления, которое в свою очередь будет наследоваться от класса Observer. Контроллер должен будет реализовывать метод Execute(), который будет принимать объект класса Command. Представление, в свою очередь, должно будет предоставить реализацию метода Draw().

Итого, интерфейсные классы будут выглядеть следующим образом(т.к. модель требует только наследования от Observable — здесь я её не указываю):

class Command
{
public:
virtual void run(PlayerModel* model) =0;
};

class Controller
{
public:
virtual void execute(Command* command) =0;
};

class View: public Observer
{
public:
virtual void Draw() =0;
};

Сам пример реализации


В качестве примера реализации легче всего будет взять класс игрока (объект, который будет задаваться координатами x и y), которого можно будет двигать и отображать на экране:

class PlayerModel: public Observable
{
int m_x, m_y;
public:
PlayerModel(int x, int y): m_x(x), m_y(y){}
int GetX(){
return m_x;
}
int GetY(){
return m_y;
}
void IncX(){
m_x++;
Notify();
}
void IncY(){
m_y++;
Notify();
}
};

class MoveCommand: public Command
{
int m_type;
public:
typedef enum {UP, RIGHT} direction;
MoveCommand(int type): m_type(type){}
void run(PlayerModel* model){
switch (m_type){
case MoveCommand::UP:
if (model->GetY() < 640)
model->IncY();
break;
case MoveCommand::RIGHT:
if (model->GetX() < 800)
model->IncX();
break;
}
}
};

class PlayerController: public Controller
{
PlayerModel* m_model;
public:
PlayerController(PlayerModel* model): m_model(model){}
void execute(Command* command){
command->run(m_model);
}
};

class PlayerView: public View
{
class Circle
{
int m_x, m_y;
int rad;
public:
void Draw(){
cout << «I'm circle, with coordinates » << m_x
<< " and " << m_y << "!" << endl;
}
void SetX(int x){
m_x = x;
}
void SetY(int y){
m_y = y;
}
} *m_circle;
public:
PlayerView(){
m_circle = new Circle();
}
~PlayerView(){
delete(m_circle);
}
void Draw(){
m_circle->Draw();
}
void Update(Observable* obs, Argument *arg){
PlayerModel* model = static_cast<PlayerModel*>(obs);
m_circle->SetX(model->GetX());
m_circle->SetY(model->GetY());
Draw();
}
};

int main()
{
PlayerModel* player = new PlayerModel(300, 500);
Controller* playerController = new PlayerController(player);
View* playerView = new PlayerView();
Command *up = new MoveCommand(MoveCommand::UP),
*right = new MoveCommand(MoveCommand::RIGHT);
player->AddObs(playerView);
playerController->execute(up);
playerController->execute(right);
delete player;
delete playerController;
delete playerView;
delete up, down, right, left;
}


(для экономии места реализацию паттерна Observer не выкладываю)

Как можно заметить главная зависимость Представления от Модели существует только в методе Update. В этом методе мы переводим данные модели на уровень данных Представления. Таким образом, можно менять способы Представления, совершенно не беспокоясь об исходной моделе.

Контроллер содержит указатель на модель, которую следует изменить, он же проверяет данные и сам выбирает нужно ли влиять на модель. Таким образом можно изменять логику работы приложения не затрагивая структуру модели.

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

Для большего удобства работы с MVC можно будет воспользоваться паттерном Facade/Фасад, либо агрегировать Контроллер в Представление (очевидно, что при этом повысится связь между ними).
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.