关于在创建GUI时将视图与逻辑分离的问题,我有很多问题要回答
下面是一个使用"Humble dialog"方法对一个有标签和按钮的简单对话框所做操作的最小示例。按下按钮应该会在标签上显示一些文本。我用过C++,这是一个我很熟悉的Qt,但我想它对所有其他观众来说都是可读的
在任何情况下,由于语言的选择,我对可能的副作用感兴趣(我在项目中使用C++,我有兴趣介绍它)。
class IView {
public:
IView(){}
virtual ~IView(){}
virtual void showResult(const QString &text)=0;
};
class Presenter {
public:
Presenter(IView *view){
m_View = view;
}
~Presenter(){}
void buttonPressed(){
QString text;
// Evaluate text
m_View->showResult(text);
}
private:
IView *m_View;
}
// Multiple inheritance. Is this OK?
class MyView : public QDialog, public IView {
public:
MyView(){
m_Presenter = new Presenter(this);
m_Button = new QPushbutton(this);
m_Label = new QLabel(this);
// Ui event handled inside view but then directly
// propagated to the Presenter
connect(m_Button,SIGNAL(clicked()),this,SLOT(buttonPressed()));
}
~MyView(){
delete m_Presenter;
// Qt will automatically delete m_Button and m_Label;
}
void showResult(const QString &text){
m_Label->setText(text);
}
protected slots:
void buttonPressed(){
m_Presenter->buttonPressed();
}
private:
Presenter *m_Presenter;
QPushbutton *m_Button;
QLabel *m_Label;
}
class TestView : public IView {
public:
TestView(){}
~TestView(){}
void showResult(const QString &text){
m_LabelText = text;
}
QString getResult(){
return m_LabelText;
}
private:
QString m_LabelText;
}
// Test code
TestView view;
Presenter presenter(&view);
presenter.buttonPressed();
EXPECT_EQ(view.getResult(),"Expected Result");
// Procuction code
MyView view;
view.show();
现在,这是我通过跟随费瑟关于谦逊对话的最初工作而得到的。我从Fowler的实现中得到的方法是避免在MyView的构造函数中创建Presenter类的实例,而是将其作为参数传递,这样生产代码看起来就像测试代码。我个人喜欢我在这里介绍的方法。
所以,
- 它是否意味着要与多重继承一起使用(请参阅我在MyView类中的注释)
- 事件应该直接传播到演示者,还是应该在将调用相应演示者操作的视图中处理(正如我在这里所做的那样,以避免必须将演示者设置为QObject,从而使其能够处理UI事件)
- 还有其他意见吗
当您对QObjects进行多重继承时,继承列表中的第一个类需要是QObject派生类。只有当你计划在课堂上添加信号和插槽时,这才是严格要求的,但无论如何,这都是很好的做法。所以你的班级申报:
类MyView:公共IView,公共QDialog{
需要变成这样:
类MyView:公共QDialog,公共IView{
同样,如果你在"MyView"中添加了一个插槽或信号,这只会咬你一口。
除此之外,我认为这是一个很好的实现,尽管对对话来说是一个巨大的过度消耗。:)
我用的是福勒的MVP和Qt,效果不错。事情更容易测试(nUnit风格),但它有点复杂。
我通常在C#Winforms的东西中使用相同的UI模式。
实际上,您并没有真正在这里进行多重继承。您从中继承的一个类只是一个空接口。这里唯一的问题是C++不知道类和接口之间的区别。
我认为这样在视图中创建演示者也没有问题。这不是最可测试的设计,但你可能无论如何都不会测试视图,因为如果你使用的是简陋的对话框,就没有什么可测试的了。或者,您可以通过添加第二个注入演示者的构造函数来实现"穷人的DI",而不是为了测试目的而创建它。
在C#中,我通常有完全不了解演示者的观点,只是抛出事件,而不是给演示者打电话。这增加了一些解耦,但在大多数情况下可能会过于夸张。
总的来说,这是一个很好的实现。如果我必须编写一个带有UI的C++应用程序,我会查看这篇文章
我觉得还可以。但我不会在IView接口中使用QString。如果可能的话,使用一些独立于演示文稿的类型。这样,您就可以在不影响程序逻辑和测试的情况下更改GUI工具包。只有当在QString和std::string或char*之间转换真的很痛苦时,才保留QString(我不知道…)。