Подходы к созданию представления для «скромных диалогов» - PullRequest
4 голосов
/ 27 октября 2008

У меня есть куча вопросов по поводу разделения представления от логики при создании графического интерфейса.
Ниже приведен минимальный пример того, что я бы сделал для простого диалога, в котором есть метка и кнопка с использованием подхода «Скромный диалог». При нажатии на кнопку должен появиться текст на этикетке. Я использовал 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();

Теперь это то, что я получил, следуя первоначальной работе над диалогом Humble от Feathers . Подход, который я получил бы от внедрения Фаулера , состоял бы в том, чтобы избежать создания экземпляра класса Presenter в конструкторе MyView, но вместо этого передать его в качестве параметра, чтобы производственный код выглядел как тестовый код. Мне лично нравится подход, который я представляю здесь.

Итак,

  • Предназначено ли оно для множественного наследования (см. Мой комментарий в классе MyView)?
  • Должны ли события распространяться непосредственно на докладчика или они должны обрабатываться в представлении, которое вызовет соответствующее действие докладчика (как я сделал здесь, чтобы избежать необходимости делать Presenter объектом QObject, чтобы он мог обрабатывать события пользовательского интерфейса)?
  • Есть ли другие замечания?

Ответы [ 3 ]

2 голосов
/ 01 ноября 2009

Когда вы делаете множественное наследование с QObjects, первым классом в списке наследования должен быть класс, производный от QObject. Это строго необходимо, только если вы планируете добавлять сигналы и слоты в ваш класс, но в любом случае это хорошая практика. Итак, ваше объявление класса:

класс MyView: общедоступный IView, общедоступный QDialog {

должно стать таким:

класс MyView: общедоступный QDialog, общедоступный IView {

Опять же, это укусит вас, только если вы когда-нибудь добавите слот или сигнал в «MyView».

Кроме этого, я думаю, что это хорошая реализация, хотя и огромная избыточность для диалога. :)

Я использую MVP Фаулера с Qt, и он работает нормально. Все более тестируемо (стиль nUnit), но это немного сложнее IMO.

1 голос
/ 27 октября 2008

Выглядит нормально для меня. Но я бы не использовал QString в IView-Interface. Если возможно, используйте какой-нибудь независимый от презентации тип. Таким образом, вы можете изменить GUI-инструментарий, не влияя на логику программы и тесты. Сохраняйте QString только в том случае, если действительно трудно преобразовать QString в std :: string или char * (понятия не имею ...).

1 голос
/ 27 октября 2008

Я обычно использую тот же шаблон для своего пользовательского интерфейса в C # Winforms.

Вы на самом деле здесь не делаете множественное наследование. Один из классов, от которого вы наследуете, - это просто пустой интерфейс. Единственная проблема здесь в том, что C ++ не знает разницы между классом и интерфейсом.

Я не думаю, что есть проблема с созданием докладчика внутри представления, как это. Это не самый тестируемый дизайн, но вы все равно не собираетесь тестировать представление, потому что там нечего тестировать, если вы используете скромный диалог. Или вы можете сделать «DI бедняка», добавив второй конструктор, который внедряет презентатора вместо того, чтобы создавать его для целей тестирования.

В C # у меня обычно есть представления, которые вообще не знают о докладчике, и я просто генерирую события вместо вызова докладчика. Это добавляет некоторую развязку, но в большинстве ситуаций может быть излишним.

В целом, это хорошая реализация. Если мне когда-нибудь придется написать приложение на C ++ с пользовательским интерфейсом, я собираюсь проверить этот пост

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...