用C++实现MVVM

MVVM

文章最先发表于用C++实现MVVM.
欢迎关注博客.

用C++实现MVVM

MVVM(Model-View-ViewModel)是现在比较流行的GUI程序的框架。

通过代码的编写,我谈谈我对于MVVM的理解。

整体代码的sample在Graphics Editor可以看到。

GUI库使用了QT5.9,功能代码主要使用了OpenCV库。

后面一些功能的编写不是我写的,所以代码风格可能有些不和谐,这里主要集中精力于整个框架的实现,忽略其各项功能的实现。

如果有任何理解不对的地方,欢迎您批评指出。

MVVM

阮一峰的"MVC,MVP 和 MVVM 的图示"中, 介绍了三个架构之间的区别。

总结来说,就是在Model,View,ViewModel三个模块之间,View与ViewModel之间的数据通过双向绑定进行联系,View与Model之间不产生联系,ViewModel操作Model进行数据处理。

(这里实际写代码的时候好像跟阮老师所说的有一些区别:按照阮老师所说,应该是ViewModel在功能上相当于MVP模式中的Presenter,所有逻辑都部署在这里,实际上写的时候应该是大部分逻辑都部署在Model层进行数据操作,然后通知ViewModel和View进行更新,不知道是否是在我的理解中出现问题……)

项目目录

.
├── 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

项目架构介绍

各个类以及之间关系如下:

App

class App
{
private:
    std::shared_ptr<View> view;
    std::shared_ptr<Model> model;
    std::shared_ptr<ViewModel> viewmodel;

public:
    App();
    void run();
};

在构造函数中,对各项需要初始化和绑定的数据进行绑定:


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;
};

本身提供一个用于更新的notification, 并提供get()方法交给ViewModel层进行绑定,如此可以实现ViewModel通知View进行更新。

同时,本身提供很多Command的成员变量,这些变量本省并不属于View层,本身属于ViewModel层,并在ViewModel层提供get方法给View层进行set绑定,这样就实现了View发送commandViewModel层,View就可以在不知道Command具体派生类的情况下写代码。

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层之间的通信在之前已经讲过,在构造函数中初始化具体的命令,然后get交给Viewset进行绑定。这其中有一个向基类指针的转换,我是这么写的:

 open_file_command = std::static_pointer_cast<Command, OpenFileCommand>(std::shared_ptr<OpenFileCommand> (new OpenFileCommand(std::shared_ptr<ViewModel>(this))));

然后与Model间的通信没有通过Command,而是直接获得一个Model的指针,调用它的功能函数即可。

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层本身又一个set一个notification的接口,这个notification用于通知ViewModel进行更新数据。

其他的就是针对数据的一些功能代码。

Command

本身可以写为纯虚类,我是写了一个成员变量是一个基类参数的指针,然后所有具体的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为例表示了一般的新的参数的派生方法。

common

实现了cv::MatQImage之间的转换代码。

整体流程

View层进行操作之后,会触发对应槽函数,该槽函数会准备好参数Parameter交给对应的Command,然后执行exec()这个command,exec会解出参数交给ViewModel层,ViewModel调用Model里对应的方法,进行数据操作,Model操作完之后会通知ViewModel更新显示数据,ViewModel会通知View刷新显示。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容