为“简单对话”创建视图的方法

时间:2008-10-27 09:50:10

标签: c++ unit-testing user-interface qt

关于在创建GUI时将视图与逻辑分离的问题,我有一堆问题要发布 以下是我将使用“简单对话”方法为具有标签和按钮的简单对话框所做的最小示例。按下按钮应在标签上显示一些文字。我已经习惯使用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();

现在这是我按照initial work on the Humble dialog by Feathers得到的。我将从Fowler's implentation获得的方法是避免在MyView的构造函数中创建Presenter类的实例,而是将其作为参数传递,因此生产代码看起来像测试代码。我个人喜欢我在这里提出的方法。

所以,

  • 它是否意味着与多重继承一起使用(请参阅我在MyView类中的注释)?
  • 是否应该将事件直接传播到Presenter,还是应该在将调用相应的演示者操作的视图中处理它们(正如我在此处所做的那样,以避免必须使Presenter成为QObject以便它可以处理UI事件)?
  • 还有其他评论吗?

3 个答案:

答案 0 :(得分:2)

使用QObjects进行多重继承时,继承列表中的第一个类需要是QObject派生类。如果您计划在课堂上添加信号和插槽,则严格要求这样做,但无论如何都是很好的做法。所以你的班级声明:

类MyView:public IView,public QDialog {

需要改为:

类MyView:public QDialog,public IView {

同样,如果您向“MyView”添加了一个插槽或信号,这只会让您感到厌烦。

除此之外,我认为这是一个很好的实现,虽然对话框有大量的过度杀伤力。 :)

我正在使用Fowler的MVP与Qt,它工作正常。事情更可测试(nUnit风格),但它有点复杂的IMO。

答案 1 :(得分:1)

我通常在C#Winforms中使用相同的UI模式。

你实际上并没有在这里做多重继承。您继承的其中一个类只是一个空接口。这里唯一的问题是C ++不知道类和接口之间的区别。

我不认为在视图中创建这样的演示者也有问题。它不是最可测试的设计,但你可能还没有去测试视图,因为如果你使用简陋的对话框就没有什么可以测试的。或者你可以通过添加一个注入演示者的第二个构造函数来做“穷人的DI”,而不是为了测试目的而创建它。

在C#中,我通常会看到根本不了解演示者的视图而只是抛出事件而不是调用演示者。这增加了一些解耦,但在大多数情况下可能有点过分。

总的来说,这是一个很好的实施。如果我将不得不用一个用户界面来编写一个C ++应用程序我将要查看这篇文章

答案 2 :(得分:1)

对我来说还不错。但我不会在IView-Interface中使用QString。如果可能,请使用一些独立的演示文稿类型。这样,您可以更改GUI工具包,而不会影响程序逻辑和测试。只有在QString和std :: string或char *之间转换真的很痛苦时才保留QString(我不知道......)。