文章最先發(fā)表于用C++實(shí)現(xiàn)MVVM.
歡迎關(guān)注博客.
用C++實(shí)現(xiàn)MVVM
序
MVVM
(Model-View-ViewModel
)是現(xiàn)在比較流行的GUI程序的框架巡李。
通過(guò)代碼的編寫(xiě)抚笔,我談?wù)勎覍?duì)于MVVM的理解。
整體代碼的sample在Graphics Editor可以看到侨拦。
GUI庫(kù)使用了QT5.9
殊橙,功能代碼主要使用了OpenCV
庫(kù)。
后面一些功能的編寫(xiě)不是我寫(xiě)的狱从,所以代碼風(fēng)格可能有些不和諧膨蛮,這里主要集中精力于整個(gè)框架的實(shí)現(xiàn),忽略其各項(xiàng)功能的實(shí)現(xiàn)季研。
如果有任何理解不對(duì)的地方敞葛,歡迎您批評(píng)指出。
MVVM
在阮一峰的"MVC与涡,MVP 和 MVVM 的圖示"中惹谐, 介紹了三個(gè)架構(gòu)之間的區(qū)別。
總結(jié)來(lái)說(shuō)驼卖,就是在Model氨肌,View,ViewModel三個(gè)模塊之間酌畜,View與ViewModel之間的數(shù)據(jù)通過(guò)雙向綁定進(jìn)行聯(lián)系怎囚,View與Model之間不產(chǎn)生聯(lián)系,ViewModel操作Model進(jìn)行數(shù)據(jù)處理桥胞。
(這里實(shí)際寫(xiě)代碼的時(shí)候好像跟阮老師所說(shuō)的有一些區(qū)別:按照阮老師所說(shuō)恳守,應(yīng)該是ViewModel在功能上相當(dāng)于MVP模式中的Presenter考婴,所有邏輯都部署在這里,實(shí)際上寫(xiě)的時(shí)候應(yīng)該是大部分邏輯都部署在Model層進(jìn)行數(shù)據(jù)操作井誉,然后通知ViewModel和View進(jìn)行更新蕉扮,不知道是否是在我的理解中出現(xiàn)問(wèn)題……)
項(xiàng)目目錄
.
├── app.cpp
├── app.h
├── command.cpp
├── command.h
├── Commands
│ ├── alter_bright_command.cpp
│ ├── alter_bright_command.h
│ ├── crop_command.cpp
│ ├── crop_command.h
│ ├── detect_face_command.cpp
│ ├── detect_face_command.h
│ ├── filter_command.cpp
│ ├── filter_command.h
│ ├── open_file_command.cpp
│ ├── open_file_command.h
│ ├── reset_command.cpp
│ ├── reset_command.h
│ ├── rotate_command.cpp
│ ├── rotate_command.h
│ ├── save_bmp_command.cpp
│ ├── save_bmp_command.h
│ ├── save_file_command.cpp
│ └── save_file_command.h
├── common.cpp
├── common.h
├── GraphicsEditor.pro
├── GraphicsEditor.pro.user
├── LICENSE
├── main.cpp
├── model.cpp
├── model.h
├── MyView.cpp
├── MyView.h
├── notification.cpp
├── notification.h
├── parameters.cpp
├── parameters.h
├── README.md
├── test.pro
├── test.pro.user
├── view.cpp
├── view.h
├── viewmodel.cpp
├── viewmodel.h
└── view.ui
項(xiàng)目架構(gòu)介紹
各個(gè)類以及之間關(guān)系如下:
App
class App
{
private:
std::shared_ptr<View> view;
std::shared_ptr<Model> model;
std::shared_ptr<ViewModel> viewmodel;
public:
App();
void run();
};
在構(gòu)造函數(shù)中,對(duì)各項(xiàng)需要初始化和綁定的數(shù)據(jù)進(jìn)行綁定:
App::App():view(new View),model(new Model), viewmodel(new ViewModel)
{
viewmodel->bind(model);
view->set_img(viewmodel->get());
view->set_open_file_command(viewmodel->get_open_file_command());
view->set_alter_bright_command(viewmodel->get_alter_bright_command());
view->set_filter_rem_command(viewmodel->get_filter_rem_command());
view->set_reset_command(viewmodel->get_reset_command());
view->set_detect_face_command(viewmodel->get_detect_face_command());
view->set_save_file_command(viewmodel->get_save_file_command());
view->set_save_bmp_file_command(viewmodel->get_save_bmp_file_command());
view->set_rotate_command(viewmodel->get_rotate_command());
view->set_crop_command(viewmodel->get_crop_command());
viewmodel->set_update_view_notification(view->get_update_view_notification());
model->set_update_display_data_notification(viewmodel->get_update_display_data_notification());
}
View
class View : public QMainWindow
{
Q_OBJECT
public:
explicit View(QWidget *parent = 0);
~View();
void update();
void set_img(std::shared_ptr<QImage> image);
void set_open_file_command(std::shared_ptr<Command>);
void set_alter_bright_command(std::shared_ptr<Command>);
void set_filter_rem_command(std::shared_ptr<Command>);
void set_reset_command(std::shared_ptr<Command>);
void set_detect_face_command(std::shared_ptr<Command>);
void set_save_file_command(std::shared_ptr<Command>);
void set_save_bmp_file_command(std::shared_ptr<Command>);
void set_rotate_command(std::shared_ptr<Command>);
void set_crop_command(std::shared_ptr<Command>);
std::shared_ptr<Notification> get_update_view_notification();
private slots:
void on_button_open_clicked();
void on_brightSlider_valueChanged(int value);
void on_contrastSlider_valueChanged(int value);
void on_filter_1_clicked();
void on_reset_clicked();
void on_actionOpen_File_triggered();
void on_button_detect_face_clicked();
void on_actionSave_triggered();
void on_action_bmp_triggered();
void on_action_png_triggered();
void on_action_jpeg_triggered();
void on_rotateSlider_valueChanged(int value);
private:
Ui::View *ui;
MyView* canvas;
std::shared_ptr<QImage> q_image;
std::shared_ptr<Command> open_file_command;
std::shared_ptr<Command> alter_bright_command;
std::shared_ptr<Command> filter_rem_command;
std::shared_ptr<Command> reset_command;
std::shared_ptr<Command> detect_face_command;
std::shared_ptr<Command> save_file_command;
std::shared_ptr<Command> save_bmp_file_command;
std::shared_ptr<Command> rotate_command;
std::shared_ptr<Command> crop_command;
std::shared_ptr<Notification> update_view_notification;
};
本身提供一個(gè)用于更新的notification
, 并提供get()
方法交給ViewModel
層進(jìn)行綁定颗圣,如此可以實(shí)現(xiàn)ViewModel
通知View
進(jìn)行更新喳钟。
同時(shí),本身提供很多Command
的成員變量在岂,這些變量本省并不屬于View
層奔则,本身屬于ViewModel
層,并在ViewModel
層提供get
方法給View
層進(jìn)行set
綁定蔽午,這樣就實(shí)現(xiàn)了View
發(fā)送command
給ViewModel
層易茬,View
就可以在不知道Command具體派生類的情況下寫(xiě)代碼。
ViewModel
class ViewModel
{
private:
std::shared_ptr<QImage> q_image;
std::shared_ptr<Model> model;
std::shared_ptr<Command> open_file_command;
std::shared_ptr<Command> alter_bright_command;
std::shared_ptr<Command> filter_rem_command;
std::shared_ptr<Command> reset_command;
std::shared_ptr<Command> detect_face_command;
std::shared_ptr<Command> save_file_command;
std::shared_ptr<Command> save_bmp_file_command;
std::shared_ptr<Command> rotate_command;
std::shared_ptr<Command> crop_command;
std::shared_ptr<Notification> update_display_data_notification;
std::shared_ptr<Notification> update_view_notification;
public:
ViewModel();
void bind(std::shared_ptr<Model> model);
void exec_open_file_command(std::string path);
void exec_alter_bright_command(int nBright, int nContrast);
void exec_filter_rem_command();
void exec_reset_command();
void exec_detect_face_command();
void exec_save_file_command(std::string path);
void exec_save_bmp_file_command(std::string path);
void exec_rotate_command(int angle);
void exec_crop_command(double x_s, double y_s, double x_e, double y_e);
void set_update_view_notification(std::shared_ptr<Notification> notification);
std::shared_ptr<Command> get_open_file_command();
std::shared_ptr<Command> get_alter_bright_command();
std::shared_ptr<Command> get_filter_rem_command();
std::shared_ptr<Command> get_reset_command();
std::shared_ptr<Command> get_detect_face_command();
std::shared_ptr<Command> get_save_file_command();
std::shared_ptr<Command> get_save_bmp_file_command();
std::shared_ptr<Command> get_rotate_command();
std::shared_ptr<Command> get_crop_command();
std::shared_ptr<Notification> get_update_display_data_notification();
std::shared_ptr<QImage> get();
void notified();
};
與View
層之間的通信在之前已經(jīng)講過(guò)及老,在構(gòu)造函數(shù)中初始化具體的命令抽莱,然后get
交給View
的set
進(jìn)行綁定。這其中有一個(gè)向基類指針的轉(zhuǎn)換骄恶,我是這么寫(xiě)的:
open_file_command = std::static_pointer_cast<Command, OpenFileCommand>(std::shared_ptr<OpenFileCommand> (new OpenFileCommand(std::shared_ptr<ViewModel>(this))));
然后與Model
間的通信沒(méi)有通過(guò)Command
食铐,而是直接獲得一個(gè)Model
的指針,調(diào)用它的功能函數(shù)即可僧鲁。
Model
class Model
{
private:
cv::Mat image;
std::shared_ptr<Notification> update_display_data_notification;
public:
Model(){}
void set_update_display_data_notification(std::shared_ptr<Notification> notification);
void open_file(std::string path);
cv::Mat& get();
cv::Mat& getOrigin();
void notify();
void save_file(std::string path);
void save_bmp_file(std::string path);
void alterBrightAndContrast(int nbright, int nContrast);
void detect_face();
void filterReminiscence(); //Filter No.1
void reset();
void rotate(double angle);
void crop(int x1, int y1, int x2, int y2);
};
Model
層本身又一個(gè)set一個(gè)notification的接口虐呻,這個(gè)notification用于通知ViewModel
進(jìn)行更新數(shù)據(jù)。
其他的就是針對(duì)數(shù)據(jù)的一些功能代碼寞秃。
Command
本身可以寫(xiě)為純虛類斟叼,我是寫(xiě)了一個(gè)成員變量是一個(gè)基類參數(shù)的指針,然后所有具體的command都是派生于此春寿,提供exec()
方法朗涩。
class Command
{
protected:
std::shared_ptr<Parameters> params;
public:
Command();
void set_parameters(std::shared_ptr<Parameters> parameters){
params = parameters;
}
virtual void exec() = 0;
};
Notification
class Notification
{
public:
Notification();
virtual void exec() = 0;
};
class UpdateDisplayDataNotification: public Notification{
private:
std::shared_ptr<ViewModel> viewmodel;
public:
UpdateDisplayDataNotification(std::shared_ptr<ViewModel> vm):viewmodel(vm){}
void exec(){
viewmodel->notified();
}
};
class UpdateViewNotification: public Notification{
private:
std::shared_ptr<View> view;
public:
UpdateViewNotification(std::shared_ptr<View> v):view(v){}
void exec(){
view->update();
}
};
Parameters
class Parameters
{
public:
Parameters();
};
class PathParameters: public Parameters{
private:
std::string path;
public:
PathParameters(std::string _path):path(_path){
}
std::string get_path(){
return path;
}
};
以PathParameters
為例表示了一般的新的參數(shù)的派生方法。
common
實(shí)現(xiàn)了cv::Mat
與QImage
之間的轉(zhuǎn)換代碼绑改。
整體流程
在View
層進(jìn)行操作之后谢床,會(huì)觸發(fā)對(duì)應(yīng)槽函數(shù),該槽函數(shù)會(huì)準(zhǔn)備好參數(shù)Parameter
交給對(duì)應(yīng)的Command
绢淀,然后執(zhí)行exec()
這個(gè)command萤悴,exec會(huì)解出參數(shù)交給ViewModel
層,ViewModel
調(diào)用Model
里對(duì)應(yīng)的方法皆的,進(jìn)行數(shù)據(jù)操作覆履,Model
操作完之后會(huì)通知ViewModel
更新顯示數(shù)據(jù),ViewModel
會(huì)通知View
刷新顯示。