QT: действительно простое приложение MVC
Концепция MVC, показанная на готовых QT'шных классах, отличающихся громоздкостью, выглядит не всегда понятной :) Меж тем, её можно продемонстрировать на чём-то гораздо более стандартном, например, на матрице целых чисел, редактируемой прямо в консоли.
Попробуем всё сделать по правилам. Создадим проект QT, который будет компилироваться, как консольное приложение, то есть, файл проекта .pro имеет вид
QT += core QT -= gui TARGET = Console1 CONFIG += console CONFIG -= app_bundle TEMPLATE = app
Файлы с моделью, видом и контроллером будем добавлять в проект как обычные заголовочные файлы с расширением .h
Сначала придётся применить немного абстрактного мышления.
Класс AbstractMatrixModel (файл abstract_matrix_model.h) будет содержать только виртуальные методы, которые нужны любой конкретной модели нашей предметной области. Что должна уметь модель матрицы? Получать элемент из нужной строки и столбца, записывать этот элемент обратно, узнавать количество строк и количество столбцов матрицы. Так и скажем:
#ifndef ABSTRACT_MATRIX_MODEL_H_INCLUDED #define ABSTRACT_MATRIX_MODEL_H_INCLUDED #include <cstddef> class AbstractMatrixModel { public: virtual int getData(size_t row, size_t column) const = 0; virtual void setData(size_t row, size_t column, int value) = 0; virtual size_t columns() const = 0; virtual size_t rows() const = 0; }; #endif // ABSTRACT_MATRIX_MODEL_H_INCLUDED
С видом всё ещё проще (файл abstract_view.h). Достаточно иметь методы для вывод матрицы в консоль и для обработки событий от клавиатуры (мы собираемся редактировать в консоли нашу матрицу). Вот виртуальные прототипы будущих методов вида:
#ifndef ABSTRACT_VIEW_H_INCLUDED #define ABSTRACT_VIEW_H_INCLUDED class AbstractView { public: virtual void event(int key) = 0; virtual void show() = 0; }; #endif // ABSTRACT_VIEW_H_INCLUDED
Теперь моделируем матрицу (файл matrix_model.h). Как и положено, данные будут храниться в приватной секции класса (указатель int * matrix_), для простоты представим матрицу как вектор, всё равно все действия с её элементами инкапсулированы в методы getData и setData. Вот какой класс вышел:
#ifndef MATRIX_MODEL_H_INCLUDED #define MATRIX_MODEL_H_INCLUDED #include "abstract_matrix_model.h" class MatrixModel : public AbstractMatrixModel { private: int * matrix_; size_t rows_, cols_; public: MatrixModel(size_t rows, size_t cols) : matrix_(new int[rows * cols]()), rows_(rows), cols_(cols) {} ~MatrixModel() { delete[] matrix_; } int getData(size_t row, size_t col) const { return matrix_[col * rows_ + row]; } void setData(size_t row, size_t col, int value) { matrix_[col * rows_ + row] = value; } size_t columns() const { return cols_; } size_t rows() const { return rows_; } }; #endif // MATRIX_MODEL_H_INCLUDED
Прежде, чем писать вид, позаботимся о вспомогательных сущностях.
Класс ConsoleCursor (файл console_cursor.h) просто следит за виртуальными координатами курсора, сохранёнными в приватных целых переменных row_, col_. Сколько всего будет доступно строк и столбцов, мы пока не знаем, поэтому методы позиционирования курсора будут получать нужную информацию через параметры rows, columns.
Конечно, ему придётся также заботиться о позиционировании курсора, этим займётся статический метод setXY, используя возможности Windows API. Вот полный текст класса:
#ifndef CONSOLE_CURSOR_H_INCLUDED #define CONSOLE_CURSOR_H_INCLUDED #include <windows.h> class ConsoleCursor { private: int row_, col_; public: ConsoleCursor(int row = 0, int col = 0):row_(row),col_(col) {} void up(size_t rows) { row_ = row_ == 0 ? rows - 1 : row_ - 1; } void down(size_t rows) { row_ = size_t(row_ + 1) == rows ? 0 : row_ + 1; } void left(size_t columns) { col_ = col_ == 0 ? columns - 1 : col_ - 1; } void right(size_t columns) { col_ = size_t(col_ + 1) == columns ? 0 : col_ + 1; } int column() const { return col_; } int row() const { return row_; } void moveCursor(int x, int y) { setXY(x + col_ * 3, y + row_); } static void setXY(short x, short y) { COORD pos = { x, y }; HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleCursorPosition(output, pos); } }; #endif // CONSOLE_CURSOR_H_INCLUDED
Маленькое перечисление ConsoleKeys, которое мы лишь для порядка сохранили в отдельном файле console_keys.h, просто будет содержать коды клавиш, обрабатываемых нашим представлением:
#ifndef KEYS_H_INCLUDED #define KEYS_H_INCLUDED enum ConsoleKeys { KEY_ESC = 27, ARROW_UP = 256 + 72, ARROW_DOWN = 256 + 80, ARROW_LEFT = 256 + 75, ARROW_RIGHT = 256 + 77, KEY_ENTER = 13 }; #endif // KEYS_H_INCLUDED
Теперь, пожалуй, время писать само представление, или вид, как его часто называют (файл console_matrix_view.h). Метод show, как ему и положено, будет показывать матрицу, а event обеспечит "навигацию", сводящуюся к перемещению курсора по числам. Поможет ему в этом служебный метод input, отвечающий за ввод данных с клавиатуры. Прежде, чем что-то делать, методы смотрят, подключена ли модель (указатель model_). А поставит его, как и положено в MVC, метод setModel. Вот полный код класса:
#ifndef CONSOLE_MATRIX_VIEW_H_INCLUDED #define CONSOLE_MATRIX_VIEW_H_INCLUDED #include "abstract_view.h" #include "abstract_matrix_model.h" #include "console_cursor.h" #include "console_keys.h" #include <cstdio> class ConsoleMatrixView : public AbstractView { private: int x_, y_; ConsoleCursor cursor_; AbstractMatrixModel * model_; public: ConsoleMatrixView(int x, int y) : x_(x),y_(y),cursor_() { } /*virtual*/ void show() { if(model_) { size_t rows = model_->rows(); size_t columns = model_->columns(); for (size_t i = 0; i < rows; ++i) { for (size_t j = 0; j < columns; ++j) { ConsoleCursor(i, j).moveCursor(x_, y_); printf("%02d ", model_->getData(i, j)); } } cursor_.moveCursor(x_, y_); } } size_t columns() const { return model_ ? model_->columns() : 0; } size_t rows() const { return model_ ? model_->rows() : 0; } void setModel(AbstractMatrixModel * model) { model_ = model; } private: void input() { char buf[3] = {'0','0','0'}; if(fgets(buf, sizeof(buf), stdin) != 0) { int val; if(sscanf(buf, "%d", &val) == 1) { model_->setData(cursor_.row(), cursor_.column(), val); } show(); fflush(stdin); } } /*virtual*/ void event(int key) { if (model_) { switch(key) { case ARROW_UP: cursor_.up(rows()); cursor_.moveCursor(x_, y_); break; case ARROW_DOWN: cursor_.down(rows()); cursor_.moveCursor(x_, y_); break; case ARROW_LEFT: cursor_.left(columns()); cursor_.moveCursor(x_, y_); break; case ARROW_RIGHT: cursor_.right(columns()); cursor_.moveCursor(x_, y_); break; case KEY_ENTER: input(); break; } } } }; #endif // CONSOLE_MATRIX_VIEW_H_INCLUDED
Ах да, "триаде" же ещё контроллер нужен :) Кстати, когда модель спроектирована плохо, у неё всегда получаются огромные и непонятные контроллеры, даже есть специальный термин FSUC - "толстые тупые уродливые контроллеры" (Fat Stupid Ugly Controllers). Но наш контроллер будет тонким и умным, ведь ему хватит методов exec (запуститься) и setMainView (установить представление, с которым работаем). Вот полный код файла controller.h:
#ifndef CONTROLLER_H_INCLUDED #define CONTROLLER_H_INCLUDED #include <cstdio> #include <conio.h> #include "abstract_view.h" #include "console_keys.h" class Controller { private: AbstractView * view_; public: void exec() { if (view_) { view_->show(); int ch; while ((ch = getCode()) != KEY_ESC) { view_->event(ch); } } } void setMainView(AbstractView * view) { view_ = view; } private: static int getCode() { int ch = getch(); if (ch == 0 || ch == 224) { ch = 256 + getch(); } return ch; } }; #endif // CONTROLLER_H_INCLUDED
Теперь добавим в проект файл main.cpp, который всё это запустит "по правилам MVC":
#include "console_matrix_view.h" #include "matrix_model.h" #include "controller.h" int main() { Controller control; MatrixModel model(10, 15); //размерности ConsoleMatrixView view (1, 1); //позиция вывода view.setModel(&model); control.setMainView(&view); control.exec(); }
Войти в редактирование элемента можно по Enter, ходим стрелочками, для выхода, как видно из листинга, нажмите Esc, потом Enter. Всё готово и работает, правда, контролем правильности ввода "на лету" мы здесь не озадачивались, но проект ведь можно и развить :)
Ну и помните главное - MVC не так страшен, как его малюют, а наоборот удобен, и показанное решение - отнюдь не единственное или лучшее, наверняка можно сделать и короче, и лучше.
скриншот приложения в работе
Архив .zip с папкой этого проекта QT5 (4 Кб)
12.04.2016, 16:48 [6581 просмотр]