4

关于在创建 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();

现在,这就是我按照Feathers 对 Humble 对话框的初步工作得到的结果。我从Fowler 的实现中得到的方法是避免在 MyView 的构造函数中创建 Presenter 类的实例,而是将其作为参数传递,这样生产代码就会看起来像测试代码。我个人喜欢我在这里介绍的方法。

所以,

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

3 回答 3

2

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

类 MyView :公共 IView ,公共 QDialog {

需要改为:

类MyView:公共QDialog,公共IView {

同样,这只会在您向“MyView”添加插槽或信号时才会对您不利。

除此之外,我认为这是一个很好的实现,尽管对于对话来说有大量的矫枉过正。:)

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

于 2009-11-01T05:48:04.743 回答
1

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

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

我认为在这样的视图中创建演示者也没有问题。这不是最可测试的设计,但您可能无论如何都不会测试视图,因为如果您使用的是简陋的对话框,那里没有什么可以测试的。或者,您可以通过添加第二个构造函数来注入演示者,而不是出于测试目的创建演示者,从而执行“穷人的 DI”。

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

总的来说,这是一个很好的实现。如果我必须编写带有 UI 的 C++ 应用程序,我将查看这篇文章

于 2008-10-27T10:45:53.377 回答
1

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

于 2008-10-27T11:38:10.383 回答