C ++中的MVC和Subject-Observer模式& QT

时间:2016-04-05 17:26:58

标签: c++ qt model-view-controller

声明:

正如第一个答案已经适当注意到的那样,在当前的示例案例中使用MVC是过度的。问题的目标是通过一个简单的例子来理解底层概念,以便能够在更大的程序中使用它们,在这个程序中修改更复杂的数据(数组,对象)。

我正在尝试用C ++和C ++实现MVC模式。 QT,类似于这里的问题:

Other MVC Questions

该程序有两行编辑:

  • mHexLineEdit
  • mDecLineEdit

3个按钮

  • mConvertToHexButton
  • mConvertoDecButton
  • mClearButton

并且只修改字符串。

enter image description here

与另一个问题的区别在于,我试图实现Subject / Observer模式,以便在模型更改后更新View。

Model.h

#ifndef MODEL_H
#define MODEL_H

#include <QString>
#include <Subject>

class Model : virtual public Subject
{
public:
    Model();
    ~Model();
    void convertDecToHex(QString iDec);
    void convertHexToDec(QString iHex);
    void clear();

    QString getDecValue() {return mDecValue;}
    QString getHexValue() {return mHexValue;}
private:
    QString mDecValue;
    QString mHexValue;
};
#endif // MODEL_H

Model.cpp

#include "Model.h"

Model::Model():mDecValue(""),mHexValue(""){}
Model::~Model(){}

void Model::convertDecToHex(QString iDec)
{
    mHexValue = iDec + "Hex";

    notify("HexValue");
}

void Model::convertHexToDec(QString iHex)
{
    mDecValue = iHex + "Dec";

    notify("DecValue");
}

void Model::clear()
{
  mHexValue = "";
  mDecValue = "";

  notify("AllValues");
}

View.h

#ifndef VIEW_H
#define VIEW_H

#include <QtGui/QMainWindow>
#include "ui_View.h"
#include <Observer>

class Controller;
class Model;
class View : public QMainWindow, public Observer
{
    Q_OBJECT

public:
    View(QWidget *parent = 0, Qt::WFlags flags = 0);
    ~View();
    void setController(VController* iController);
    void setModel(VModel* iModel);
    QString getDecValue();
    QString getHexValue();
public slots:
    void ConvertToDecButtonClicked();
    void ConvertToHexButtonClicked();
    void ClearButtonClicked();
private:

    virtual void update(Subject* iChangedSubject, std::string iNotification);

    Ui::ViewClass ui;

    Controller*  mController;
    Model*        mModel;
};

#endif // VIEW_H

View.cpp

#include "View.h"
#include "Model.h"
#include "Controller.h"
#include <QSignalMapper>

VWorld::VWorld(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
    ui.setupUi(this);

    connect(ui.mConvertToHexButton,SIGNAL(clicked(bool)),this,SLOT(ConvertToHexButtonClicked()));
    connect(ui.mConvertToDecButton,SIGNAL(clicked(bool)),this,SLOT(ConvertToDecButtonClicked()));
    connect(ui.mClearButton,SIGNAL(clicked(bool)),this,SLOT(ClearButtonClicked()));
}

View::~View(){}

void View::setController(Controller* iController)
{
    mController = iController;

    //connect(ui.mConvertToHexButton,SIGNAL(clicked(bool)),this,SLOT(mController->OnConvertToHexButtonClicked(this)));
    //connect(ui.mConvertToDecButton,SIGNAL(clicked(bool)),this,SLOT(mController->OnConvertToDecButtonClicked(this)));
    //connect(ui.mClearButton,SIGNAL(clicked(bool)),this,SLOT(mController->OnClearButtonClicked(this)));
}

void View::setModel(Model* iModel)
{
    mModel = iModel;

    mModel->attach(this);
}

QString View::getDecValue()
{
    return ui.mDecLineEdit->text();
}

QString View::getHexValue()
{
    return ui.mHexLineEdit->text();
}

void View::ConvertToHexButtonClicked()
{
    mController->OnConvertToHexButtonClicked(this);
}

void View::ConvertToDecButtonClicked()
{
    mController->OnConvertToDecButtonClicked(this);
}

void VWorld::ClearButtonClicked() 
{
    mController->OnClearButtonClicked(this);
}

void View::update(Subject* iChangedSubject, std::string     iNotification)
{
    if(iNotification.compare("DecValue") == 0)
    {
        ui.mDecLineEdit->setText(mModel->getDecValue());
    }
    else if(iNotification.compare("HexValue") == 0)
    {
        ui.mHexLineEdit->setText(mModel->getHexValue());
    }
    else if(iNotification.compare("AllValues") == 0)
    {
        ui.mDecLineEdit->setText(mModel->getDecValue());
        ui.mHexLineEdit->setText(mModel->getHexValue());
    }
    else
    {
        //Unknown notification;
    }
}

或者Controller.h

#ifndef CONTROLLER_H
#define CONTROLLER_H

//Forward Declaration
class Model;
class View;

class Controller 
{
public:
    Controller(Model* iModel);
    virtual ~Controller();
    void OnConvertToDecButtonClicked(View* iView);
    void OnConvertToHexButtonClicked(View* iView);
    void OnClearButtonClicked(View* iView);
private:
    Model* mModel;
};
#endif // CONTROLLER_H

Controller.cpp

#include "Controller.h"
#include "Model.h"
#include "View.h"

Controller::Controller(Model* iModel):mModel(iModel){}

Controller::~Controller(){}

void Controller::OnConvertToDecButtonClicked(View* iView) 
{
  QString wHexValue = iView->getHexValue();

  mModel->convertHexToDec(wHexValue);
}

void Controller::OnConvertToHexButtonClicked(View* iView) 
{
  QString wDecValue = iView->getDecValue();

  mModel->convertDecToHex(wDecValue);
}

void Controller::OnClearButtonClicked(View* iView) 
{
  mModel->clear();
}

的main.cpp

#include "View.h"
#include "Model.h"
#include "Controller.h"
#include <QtGui/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Model wModel;
    View wView;
    Controller wCtrl(&wModel);
    wView.setController(&wCtrl);
    wView.setModel(&wModel);
    wView.show();
    return a.exec();
}

如果它们变得相关,我可以稍后发布主题/观察者文件。

除了一般性意见外,有人可以回答这些问题:

1)将按钮信号直接连接到控制器插槽(如View::setController中注释掉的部分)会更好吗? Controller需要知道哪个View被调用,所以它可以使用来自View的正确信息呢?这意味着:

a)Reimplement a QSignalMapper

b)Upgrade to Qt5 and VS2012 in order to connect directly with lambdas (C++11);

2)在模型调用更新时,了解更改内容的最佳方法是什么?它是一个切换/循环所有可能性,一个预定义的地图......?

3)另外,我是否应该通过更新功能传递必要的信息,或者让View在收到通知后检查模型所需的值?

在第二种情况下,View需要访问Model数据......

编辑:

特别是在修改了很多数据的情况下。例如,有一个加载按钮,整个对象/数组被修改。通过信号/插槽机制将副本传递给视图将非常耗时。

来自ddriver的回答

  

现在,如果您有一个传统的“项目列表”模型并且您的视图是列表/树/表,那么将是另一回事,但您的案例是单一表单之一。

4)视图是否需要引用模型?因为它只对控制器起作用? (View :: setModel())

如果没有,它如何将自己注册为模型的观察者?

4 个答案:

答案 0 :(得分:3)

你正在过度思考几乎无足轻重的事情。你也过度工程了。

是的,从UI中抽象逻辑总是一个好主意,但在您的特定示例中,数据的额外抽象层不是必需的,主要是因为您没有不同的数据集,您只有两个值实际上是逻辑的一部分,不值得数据抽象层。

现在,如果您有一个传统的“项目列表”模型并且您的视图是列表/树/表,那么将是另一回事,但您的案例是单一表单之一。

在您的情况下,正确的设计将是Converter类,其中包括您当前的模型数据,控制器和转换逻辑,以及ConverterUI类,它基本上是您的视图形式。您可以节省样板代码和组件互连。

话虽这么说,你可以自由地经历不必要的长度和过度杀伤。

1 - 您将视图中的修改数据发送到控制器连接,因此它始终来自相应的视图,控制器不关心它是哪个视图,可能有多少视图,或者是否存在一点看。 QSignalMapper是一个选项,但它相当有限 - 它只支持单个参数,只支持少量参数类型。老实说,我自己更喜欢一个线路插槽,它们更灵活,并且不是那么难写,而且它们是可重复使用的代码,有时会很方便。 Lambdas是一个很酷的新功能,使用它们肯定会让你看起来更酷,但在你的特殊情况下,它们不会产生那么大的差别,而lambdas本身并不值得转换到Qt5。话虽如此,除了lambdas之外,还有更多理由更新到Qt5。

2 - 信号和插槽,你知道你在编辑什么,所以你只更新那个

3 - 通过信号传递值更优雅,并且它不需要您的控制器保持对视图的引用并管理它是哪个视图,如1中所述

4 - 从MVC图中可以明显看出,该视图仅提供了用于读取的模型。所以,如果你想要一本“按书”MVC,那就是你所需要的。

enter image description here

我在前面的例子中已经改进了(有些,仍然未经测试),现在有Data这只是一个常规结构,你绝对不希望它是QObject派生的,如果你是会有很多这些,因为QObject是巨大的内存开销,Model维护着一个数据集,Controller迭代底层Model数据集并读取和写入数据,绑定到控制器的ViewApp,它为模型和两个独立的控制器和两个独立的视图组合在一起。功能有限 - 您可以转到下一个可用的数据集条目,修改或删除,在此示例中没有添加或重新排序,您可以将它们作为练习来实现。更改将传播回模型,从而反映在每个控制器和关联的视图中。您可以将多个不同的视图绑定到单个控制器。控制器的模型目前是固定的,但如果要更改它,则必须执行类似于为视图设置控制器的过程 - 也就是说,在连接到新控制器之前断开旧控制器,尽管如果您是删除旧的,会自动断开连接。

struct Data {
    QString d1, d2;
};

class Model : public QObject {
    Q_OBJECT
    QVector<Data> dataSet;
  public:
    Model() {
      dataSet << Data{"John", "Doe"} << Data{"Jane", "Doe"} << Data{"Clark", "Kent"} << Data{"Rick", "Sanchez"};
    }
    int size() const { return dataSet.size(); }
  public slots:
    QString getd1(int i) const { return i > -1 && i < dataSet.size() ? dataSet[i].d1 : ""; }
    QString getd2(int i) const { return i > -1 && i < dataSet.size() ? dataSet[i].d2 : ""; }
    void setd1(int i, const QString & d) {
      if (i > -1 && i < dataSet.size()) {
        if (dataSet[i].d1 != d) {
          dataSet[i].d1 = d;
          emit d1Changed(i);
        }
      }
    }
    void setd2(int i, const QString & d) {
      if (i > -1 && i < dataSet.size()) {
        if (dataSet[i].d2 != d) {
          dataSet[i].d2 = d;
          emit d2Changed(i);
        }
      }
    }
    void remove(int i) {
      if (i > -1 && i < dataSet.size()) {
        removing(i);
        dataSet.remove(i);
        removed();
      }
    }
  signals:
    void removing(int);
    void removed();
    void d1Changed(int);
    void d2Changed(int);
};

class Controller : public QObject {
    Q_OBJECT
    Model * data;
    int index;
    bool shifting;
  public:
    Controller(Model * _m) : data(_m), index(-1), shifting(false) {
      connect(data, SIGNAL(d1Changed(int)), this, SLOT(ond1Changed(int)));
      connect(data, SIGNAL(d2Changed(int)), this, SLOT(ond2Changed(int)));
      connect(data, SIGNAL(removing(int)), this, SLOT(onRemoving(int)));
      connect(data, SIGNAL(removed()), this, SLOT(onRemoved()));
      if (data->size()){
        index = 0;
        dataChanged();
      }
    }
  public slots:
    QString getd1() const { return data->getd1(index); }
    QString getd2() const { return data->getd2(index); }
    void setd1(const QString & d) { data->setd1(index, d); }
    void setd2(const QString & d) { data->setd2(index, d); }
    void remove() { data->remove(index); }
  private slots:
    void onRemoving(int i) { if (i <= index) shifting = true; }
    void onRemoved() {
      if (shifting) {
        shifting = false;
        if ((index > 0) || (index && !data->size())) --index;
        dataChanged();
      }
    }
    void ond1Changed(int i) { if (i == index) d1Changed(); }
    void ond2Changed(int i) { if (i == index) d2Changed(); }
    void fetchNext() {
      if (data->size()) {
        index = (index + 1) % data->size();
        dataChanged();
      }
    }
  signals:
    void dataChanged();
    void d1Changed();
    void d2Changed();
};

class View : public QWidget {
    Q_OBJECT
    Controller * c;
    QLineEdit * l1, * l2;
    QPushButton * b1, * b2, * bnext, * bremove;
  public:
    View(Controller * _c) : c(nullptr) {
      QVBoxLayout * l = new QVBoxLayout;
      setLayout(l);
      l->addWidget(l1 = new QLineEdit(this));
      l->addWidget(b1 = new QPushButton("set", this));
      connect(b1, SIGNAL(clicked(bool)), this, SLOT(setd1()));
      l->addWidget(l2 = new QLineEdit(this));
      l->addWidget(b2 = new QPushButton("set", this));
      connect(b2, SIGNAL(clicked(bool)), this, SLOT(setd2()));
      l->addWidget(bnext = new QPushButton("next", this));
      l->addWidget(bremove = new QPushButton("remove", this));
      setController(_c);
    }
    void setController(Controller * _c) {
      if (_c != c) {
        if (c) {
          disconnect(c, SIGNAL(d1Changed()), this, SLOT(updateL1()));
          disconnect(c, SIGNAL(d2Changed()), this, SLOT(updateL2()));
          disconnect(c, SIGNAL(dataChanged()), this, SLOT(updateForm()));
          disconnect(bnext, SIGNAL(clicked(bool)), c, SLOT(fetchNext()));
          disconnect(bremove, SIGNAL(clicked(bool)), c, SLOT(remove()));
          c = nullptr;
        }
        c = _c;
        if (c) {
          connect(c, SIGNAL(d1Changed()), this, SLOT(updateL1()));
          connect(c, SIGNAL(d2Changed()), this, SLOT(updateL2()));
          connect(c, SIGNAL(dataChanged()), this, SLOT(updateForm()));
          connect(bnext, SIGNAL(clicked(bool)), c, SLOT(fetchNext()));
          connect(bremove, SIGNAL(clicked(bool)), c, SLOT(remove()));
        }
      }
      updateForm();
    }
  public slots:
    void updateL1() { l1->setText(c ? c->getd1() : ""); }
    void updateL2() { l2->setText(c ? c->getd2() : ""); }
    void updateForm() {
      updateL1();
      updateL2();
    }
    void setd1() { c->setd1(l1->text()); }
    void setd2() { c->setd2(l2->text()); }
};

class App : public QWidget {
    Q_OBJECT
    Model m;
    Controller c1, c2;
  public:
    App() : c1(&m), c2(&m) {
      QVBoxLayout * l = new QVBoxLayout;
      setLayout(l);          
      l->addWidget(new View(&c1));          
      l->addWidget(new View(&c2));
    }
};

答案 1 :(得分:3)

我将在Passive-View和Model-View-Presenter

的上下文中回答这个问题

Model View Presenter

(see Wikipedia)

  

是模型 - 视图 - 控制器(MVC)架构模式的派生,主要用于构建用户界面。

模特

必须观察对模型/主题的更改。大多数主题/观察者细节都由信号/槽机制处理,因此对于这个简单的用例,通过给出一个发出值的信号来使模型可观察就足够了。因为在线编译器不支持Qt,所以我将使用boost :: signals2和std :: string。

class Model
{
public:

    Model(  )
    {
    }

    void setValue( int value )
    {
        value_ = value;
        sigValueChanged( value_ );
    }

    void clear()
    {
        value_ = boost::none;
        sigValueChanged( value_ );
    }

    boost::optional<int> value() const
    {
        return value_;
    }

    boost::signals2::signal< void( boost::optional<int> ) > sigValueChanged;

private:

    boost::optional<int> value_;
};

演示者

此处Presenter是Observer,而不是View。演示者的工作是将模型的整数值转换为文本表示以供显示。这里我们确实有两个控制器,一个用于十进制表示法,一个用于十六进制表示法。虽然可能过度设计了这个简单的情况,但我们为Presenter创建了一个抽象的基类。

class AbstractPresenter
{
public:

    AbstractPresenter()
        : model_( nullptr )
        , view_( nullptr )
    {
    }

    void setModel( Model& model )
    {
        model_ = &model;
        model.sigValueChanged.connect( [this]( int value ){
            _modelChanged( value ); } );
    }

    void setView( TextView& view )
    {
        view_ = &view;
    }

    void editChanged( std::string const& hex )
    {
        _editChanged( hex );
    }

private:

    virtual void _editChanged( std::string const& ) = 0;
    virtual void _modelChanged( boost::optional<int> ) = 0;

protected:

    Model *model_;
    TextView  *view_;
};

和十进制Presenter的实现

class DecPresenter
    : public AbstractPresenter
{
    void _editChanged( std::string const& dec ) override
    {
        int value;
        std::istringstream( dec ) >> value;

        model_->setValue( value );
    }

    void _modelChanged( boost::optional<int> value ) override
    {
        std::string text;

        if( value )
        {            
            text = std::to_string( *value );;
        }

        view_->setEdit( text );
    }
};

和十六进制情况的实现。

class HexPresenter
    : public AbstractPresenter
{
    void _editChanged( std::string const& hex ) override
    {
        int value;
        std::istringstream( hex ) >> std::hex >> value;

        model_->setValue( value );
    }

    void _modelChanged( boost::optional<int> value ) override
    {
        std::string text;

        if( value )
        {
            std::stringstream stream;
            stream << std::hex << *value;

            text = stream.str();
        }

        view_->setEdit( text );
    }
};

最后是聚合的演示者

class Presenter
{
public:

    Presenter()
        : model_( nullptr )
    {
    }

    void setModel( Model& model )
    {
        model_ = &model;
        hexPresenter.setModel( model );
        decPresenter.setModel( model );    
    }

    void setView( View& view )
    {
        hexPresenter.setView( view.hexView );
        decPresenter.setView( view.decView );    
    }

    HexPresenter hexPresenter;
    DecPresenter decPresenter;    

    void clear()
    {
        model_->clear();
    }

private:

    Model * model_;
};

视图

仅视图作业显示文本值,因此我们可以对两种情况使用相同的视图。

class TextView
{
public:

    TextView( std::string which )
        : which_( which )
    {
    }

    void setPresenter( AbstractPresenter& presenter )
    {
        presenter_ = &presenter;
    }

    void setEdit( std::string const& string )
    {
        std::cout << which_ << " : " << string << "\n";
    }

private:

    AbstractPresenter* presenter_;
    std::string which_;
};

聚合视图。

class View
{
public:

    View()
        : hexView( "hex" )
        , decView( "dec" )
    {
    }

    TextView hexView;
    TextView decView;
};

在Qt应用程序中,每个视图都有一个指向相应标签的指针,它将设置标签的文本。

    void setEdit( std::string const& string )
    {
        label->setText( QSting::fromStdString( string ) );
    }

在此背景下,我们也可以回答问题1。

  

1)将按钮信号直接连接到Controller插槽(如View :: setController中注释掉的部分)会更好吗?

由于我们想要一个没有逻辑的“被动视图”,如果控制参数合适,则可以直接连接到控制器。如果你必须将std :: string转换为QString,你可以创建一个本地插槽来执行转换并传递值,或者在Qt5中使用lambda作为作业。

  

Controller需要知道哪个View被调用,所以它可以使用View中的正确信息呢?

不,不。如果需要做不同的事情,应该是单独的演示者或演示者,每个案例都有不同的方法。

  

2)在模型调用更新时,了解更改内容的最佳方法是什么?它是一个切换/循环所有可能性,一个预定义的地图......?

最佳方式是模型告诉观察者改变了什么。这可以通过不同的信号或包含信息的事件来完成。在这种情况下,只有一个值,所以没有区别。

  

3)另外,我是否应该通过更新功能传递必要的信息,或者让View在收到通知后检查模型所需的值?

传递信息以避免演示者中的冗余更改计算。

4)视图是否需要引用模型?

不,至少不是MVP。

这些作品可以像这样放在一起:

int main()
{
    Model model;
    Presenter presenter;
    View view;

    presenter.setModel( model );
    presenter.setView( view );

    view.decView.setPresenter( presenter.decPresenter );
    view.hexView.setPresenter( presenter.hexPresenter );

    // simulate some button presses

    presenter.hexPresenter.editChanged( "42" );
    presenter.clear();
    presenter.decPresenter.editChanged( "42" );
}

创建以下输出

hex : 42
dec : 66
hex :
dec :
hex : 2a
dec : 42

<强> Live on Coliru

答案 2 :(得分:0)

  

1)将按钮信号直接连接到Controller插槽会更好吗(比如在View :: setController中注释掉的部分)?

是的,因为你打电话的插槽只是一行。

  

Controller需要知道哪个View被调用,以便它可以使用View中的正确信息呢?

不一定。你不应该在你的信号中通过this。您应该传递已更改的数据。例如,在控制器类中,您可以拥有一个名为void SetDecValueTo(int)void SetDecValueTo(QString)的广告位,只需从您的视图中调用它,而不是传递this

  

这意味着:

     

a)重新实现QSignalMapper或

     

b)升级到Qt5和VS2012以便直接连接lambdas(C ++ 11);

如上所述,你真的不需要这个。但总的来说,lambdas是未来的方式。

  

2)在模型调用更新时,了解更改内容的最佳方法是什么?它是一个切换/循环所有可能性,一个预定义的地图......?

将相关数据传递到信号/插槽中。例如,在您的模型中,您可以发出信号void DecValueChanged(int)void HexValueChanged(int)。您可以将这些广告连接到您观看的广告位void UpdateDecValue(int)void UpdateHexValue(int)

  

3)另外,我是否应该通过更新功能传递必要的信息,或者让View在收到通知后检查模型所需的值?

见上段。

  

在第二种情况下,View需要访问Model数据......

     

4)视图是否需要引用模型?因为它只对控制器起作用? (查看::则setModel())

     

如果没有,它如何将自己注册为模型的观察者?

在这种特殊情况下,它不需要对模型进行ref。您可以在main()中执行所有连接,也可以在视图中执行此操作,而不是将ref保留在模型中。

最后,由于没有很多需要完成的控制,您可以放弃控制器类并在视图中实现其功能,这在Qt中经常进行。请参阅Model/View Programming

答案 3 :(得分:0)

不同的MVC架构存在重大差异,尤其是组件应该如何协作。我更喜欢模型和视图彼此独立以及与控制器独立的方法。这种设计的好处是您可以轻松地将模型与另一个视图一起使用,反之亦然;重复使用另一个模型的视图。

这是Apple建议的MVC组件协作:https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html

Apples MVC

那它是如何运作的?

视图。 视图可以被认为只是一个假人。它现在不涉及外部世界,只能输出由模型决定的信息表示。

模特。 这是主要组件,它是您的软件。它管理应用程序的数据,逻辑和规则。

控制器。 控制器的职责是确保模型和视图相互理解。

将视图视为您的身体,将模型视为您的大脑(您是谁),控制器将是来自您大脑的电子信号。

示例

我目前没有编译器方便,所以无法测试,但我会在明天上班时尝试。需要注意的重要部分是视图和模型彼此独立且与控制器无关。

<强>模型

class PersonModel : public QObject
{
    Q_OBJECT

    QString m_sFirstname;
    QString m_sLastname;

public:
    Model() : m_sFirstname(""), m_sLastname("")
    {}
    ~Model();

    void setFirstname(const QString & sFirstname)
    {
        m_sFirstname = sFirstname;
        emit firstnameChanged(sFirstname);
    }

    void setLastname(const QString & sLastname)
    {
        m_sLastname = sLastname;
        emit lastnameChanged(sLastname);
    }

signals:
    void firstnameChanged(const QString &);
    void lastnameChanged(const QString &);
};

查看

class PersonView : public QWidget
{
    Q_OBJECT

    QLabel * m_pFirstnameLabel; // should be unique_ptrs
    QLabel * m_pLastnameLabel;  //

public:
    PersonView() :
        m_pFirstnameLabel(new QLabel),
        m_pLastnameLabel(new QLabel)
    {
        auto m_pMainLayout = new QHBoxLayout;
        m_pMainLayout->addWidget(m_pFirstnameLabel);
        m_pMainLayout->addWidget(m_pLastnameLabel);
        setLayout(m_pMainLayout);
    }

    ~PersonView()
    {
        delete m_pFirstnameLabel;
        delete m_pLastnameLabel;
    }

public slots:
    void setFirstnameText(const QString & sFirstname)
    {
        m_pFirstnameLabel->setText(sFirstname);
    }

    void setLastnameText(const QString & sLastname)
    {
        m_pLastnameLabel->setText(sLastname);
    }
};

<强>控制器

class PersonController : public QObject
{
    Q_OBJECT

    PersonView * m_pPersonView;     // better off as unique ptr
    PersonModel * m_pPersonModel;

public:
    PersonController() :
        m_pPersonView(new PersonView),
        m_pPersonModel(new PersonModel)
    {
        connect(m_pPersonModel, &PersonModel::firstnameChanged, m_pPersonView, &PersonView::setFirstnameText);
        connect(m_pPersonModel, &PersonModel::lastnameChanged, m_pPersonView, &PersonView::setLastnameText);

        m_pPersonModel->setFirstname("John");
        m_pPersonModel->setLastname("Doe");

        m_pPersonView->show();

    }

    ~PersonController()
    {
        delete m_pPersonView;
        delete m_pPersonModel;
    }
};

备注

  • 在一个更大的项目中,可能不仅仅有一个MVC。在这种情况下,通信将通过控制器进行。

  • 还可以使用一个控制器添加其他模型和视图。例如,可以使用多个视图以不同方式显示一组数据。

  • 正如我在开头提到的那样,还有其他的MVC体系结构。例如,ddriver建议作为答案。