2016-04-05 18 views
5

responsabilità:MVC e Oggetto Observer modello in C++ e QT

Mentre le prime risposte hanno preso atto, utilizzando MVC nel caso esempio attuale è eccessivo. L'obiettivo della domanda è comprendere i concetti sottostanti, con un semplice esempio, per essere in grado di usarli in un programma più grande in cui vengono modificati i dati più complessi (matrici, oggetti).


Sto cercando di implementare il pattern MVC in C++ & QT, simile alla domanda qui:

Other MVC Questions

Il programma ha 2 linee edita:

  • mHexLineEdit
  • mDecLineEdit

3 pulsanti

  • mConvertToHexButton
  • mConvertoDecButton
  • mClearButton

e modifica solo le stringhe.

enter image description here

La differenza con l'altra domanda è che sto cercando di implementare il modello Soggetto/Observer per aggiornare la vista una volta che il modello è cambiato.

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.he

#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(); 
} 

Posso postare i file Subject/Observer in un secondo momento se diventano rilevanti.

Asides da osservazioni di carattere generale, qualcuno può rispondere a queste domande:

1) sarebbe meglio collegare i segnali pulsanti per gli slot del controller direttamente (come nella parte commentata in View::setController)? Il controller deve sapere quale vista è stata chiamata in modo che possa utilizzare le informazioni corrette dalla vista, no? Questo significherebbe uno:

a) Reimplement a QSignalMapper o

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

2) Qual è il modo ottimale per sapere cosa è cambiato quando viene chiamato l'aggiornamento dal modello? È un passaggio/looping attraverso tutte le possibilità, una mappa predefinita ...?

3) Inoltre, devo passare le informazioni necessarie attraverso la funzione di aggiornamento, o lasciare che View verifichi i valori richiesti del Modello una volta che viene notificato?

Nel secondo caso la vista ha bisogno di accedere ai dati del modello ...


EDIT:

In particolare nel caso in cui v'è un sacco dei dati modificati. Ad esempio, c'è un pulsante di caricamento e un intero oggetto/array viene modificato. Passare una copia alla vista attraverso il meccanismo segnale/slot richiederebbe molto tempo.

Dalla risposta di ddriver

Ora, sarebbe una questione diversa se si dispone di un "elenco di elementi" tradizionale modello e la vostra vista è una lista/albero/tavolo, ma il tuo caso è uno di una forma singola


4) Qualora il View devono avere un riferimento al modello? dal momento che agisce solo con il controller? (Visualizza :: setModel())

In caso negativo, come si registra come osservatore del modello?

risposta

0

1) Sarebbe preferibile collegare direttamente i segnali dei pulsanti agli slot del controller (come nella parte commentata in Visualizza :: setController)?

Sì, dal momento che gli slot si chiama solo one-liner.

Il controller deve sapere quale vista ha chiamato in modo che possa utilizzare le informazioni corrette dalla vista, non è vero?

Non necessariamente. Non dovresti passare this nei tuoi segnali. Dovresti passare i dati che sono cambiati. Ad esempio, nella classe controller è possibile avere uno slot denominato void SetDecValueTo(int) o void SetDecValueTo(QString) e chiamarlo semplicemente dalla propria vista anziché passare this.

Ciò intende:

a) Reimplementare un QSignalMapper o

b) Aggiornamento a QT5 e VS2012 per collegare direttamente con lambda (C++ 11);

Come sopra, non hai davvero bisogno di questo. Ma in generale, le lambda sono la via del futuro.

2) Qual è il modo ottimale per sapere cosa è cambiato quando viene chiamato l'aggiornamento dal modello? È un passaggio/looping attraverso tutte le possibilità, una mappa predefinita ...?

Passare i dati rilevanti nei segnali/slot. Ad esempio, nel tuo modello puoi avere un segnale void DecValueChanged(int) e void HexValueChanged(int). Li colleghi agli slot della tua vista void UpdateDecValue(int) e void UpdateHexValue(int).

3) Inoltre, devo passare le informazioni necessarie tramite la funzione di aggiornamento, oppure lasciare che View verifichi i valori richiesti del Modello una volta che viene notificato?

Vedere il paragrafo precedente.

Nel secondo caso la vista necessità di accedere ai dati del modello ...

4) Qualora il View devono avere un riferimento al modello? dal momento che agisce solo con il controller? (Visualizza :: setModel())

In caso negativo, come si registra come osservatore del modello?

In questo caso particolare, non è necessario avere riferimento al modello. Puoi fare tutte le connessioni in main() o farlo nella vista e non mantenere il riferimento al modello.

Infine, poiché non è necessario eseguire molti controlli, è possibile rinunciare alla classe controller e implementarne la funzionalità nella vista, come spesso avviene in Qt. Vedi Model/View Programming.

+1

L'ultimo paragrafo - si confondono MVC con MVD di Qt - il secondo riguarda i modelli che sono elenchi di oggetti e lista/tabella/albero vi ews, con MVC il caso è un singolo modulo. – dtech

3

Stai pensando troppo a qualcosa di praticamente banale. Sei anche over-engineering.

Sì, è sempre una buona idea astrarre la logica dall'interfaccia utente, ma nel caso del tuo esempio particolare, il livello di astrazione extra dei dati non è necessario, principalmente perché non hai set di dati diversi, hai solo due valori che fanno veramente parte della logica e non meritano un livello di astrazione dei dati.

Ora, sarebbe diverso se si dispone di un modello tradizionale "elenco di articoli" e la vista è un elenco/albero/tabella, ma il caso è uno di un unico modulo.

Nel tuo caso, la progettazione corretta sarebbe una classe Converter che include i dati del modello corrente, la logica di controller e conversione e una classe ConverterUI, che è essenzialmente il tuo modulo di visualizzazione. Risparmi sul codice boilerplate e sull'interconnessione dei componenti.

Detto questo, si è liberi di passare attraverso lunghezze inutili e per l'eccessivo.

1 - si emettono i dati di modifica dalla vista alla connessione del controller, quindi verrà sempre dalla vista appropriata, il controller non è interessato a quale vista è, quante visualizzazioni potrebbero essere o se è presente una visione a tutti. QSignalMapper è un'opzione, ma è piuttosto limitata: supporta solo un singolo parametro e solo alcuni tipi di parametri. Preferisco sinceramente uno slot di linea, sono più flessibili e non molto difficili da scrivere, inoltre sono codice riutilizzabile, che a volte è utile. Lambdas è una nuova funzionalità, e l'uso di quelli ti farà sembrare più bello, ma nel tuo caso particolare non faranno molta differenza, e solo i lambda non meritano il passaggio a Qt5. Detto questo, ci sono molte più ragioni per aggiornare a Qt5 oltre a lambda.

2 - segnali e slot, si sa che cosa si sta modificando, in modo da aggiornare solo che

3 - passando i valori attraverso i segnali è più elegante, e non richiede il controller per mantenere un riferimento alla vista (s) e gestendo quale vista è, come discusso in 1

4 - come è ovvio dal diagramma MVC, la vista ha un riferimento al modello per sola lettura. Quindi se vuoi un MVC "by the book", è quello che ti serve.

enter image description here

ho migliorato (un po ', ancora non testata) sull'esempio precedente, vi Data che è solo una struttura regolare, sicuramente non si vuole che sia un QObject derivato, se avete intenzione di molti di questi, come QObject è un enorme overhead di memoria, lo Model che mantiene un set di dati, lo che itera il sottostante set dati Model e legge e scrive dati, lo View collegato a un controller e lo App che riunisce un modello e due controllori indipendenti per esso e due viste indipendenti. Le funzionalità sono limitate: puoi passare alla successiva voce di data set disponibile, modificarla o rimuoverla, non c'è aggiunta o riordino in questo esempio, potresti implementare quelli come esercizio. Le modifiche si propagheranno nuovamente al modello e quindi si rifletteranno in ogni controller e vista associata. È possibile avere più viste diverse legate a un singolo controller. Il modello di un controller è correntemente fisso, ma se si desidera cambiarlo, è necessario eseguire una procedura simile all'impostazione del controller per una vista, ovvero disconnettere il precedente prima di connettersi a quello nuovo, anche se si è cancellando quello vecchio, si disconnetterà automaticamente.

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)); 
    } 
}; 
+0

La cosa del convertitore è solo un esempio molto semplice per comprendere la logica sottostante, questa deve essere usata in un programma molto più grande. Leggerò attentamente la tua risposta ora. ;) – Smash

+0

Bene, non sarete in grado di comprendere la logica con un esempio che non coinvolge set di dati, poiché la visualizzazione e la modifica di set di dati è proprio l'intento di MVC. Inoltre, non si sentono obbligati ad attenersi a un particolare paradigma, o addirittura a una propria idea, il design MS MVC è dettato dalle caratteristiche e dai limiti del proprio set di strumenti. A meno che tu non abbia un capo sciocco che non sa nulla ma che abbia letto qualcosa e ti stia facendo fare esattamente quella cosa, dovresti cercare quello che comprendi e considerare un design pulito ed efficiente. – dtech

+0

@Smash Ho aggiunto un esempio di base, tenere presente che non è stato testato a fondo. – dtech

0

Esiste una variazione significativa delle diverse architetture MVC, in particolare il modo in cui i componenti devono collaborare. Preferisco un approccio in cui il modello e la vista sono indipendenti l'uno dall'altro e dal controller. Il vantaggio di questo design è che è possibile riutilizzare facilmente il modello con un'altra vista o viceversa; riutilizzare una vista con un altro modello.

Questa è la collaborazione componente MVC che Apple suggerisce: https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html

Apples MVC

Quindi, come funziona?

The View. La vista può essere considerata solo un manichino.Ora non ha nulla a riguardo del mondo esterno e può solo fornire una rappresentazione di informazioni che sarà decisa dal modello.

Il modello. Questo è il componente principale, è il software. Gestisce i dati, la logica e le regole dell'applicazione.

Il controller. La responsabilità del controller è assicurarsi che il modello e la vista si capiscano.

Pensa alla vista come al tuo corpo, al modello del tuo cervello (chi sei) e al controller saranno i segnali elettrici da e per il tuo cervello.

Esempio

Io attualmente non ho un compilatore a portata di mano, quindi non posso verificare questo, ma ci proverò quando arrivo al lavoro domani. La parte importante da notare è che sia la vista che il modello sono indipendenti l'uno dall'altro e dal controller.

Modello

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 &); 
}; 

View

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); 
    } 
}; 

controller

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; 
    } 
}; 

Non es

  • In un progetto più grande ci sarà probabilmente più di un solo MVC. In tal caso la comunicazione avverrà attraverso i controller.

  • È anche possibile aggiungere ulteriori modelli e viste, utilizzando un controller. Ad esempio, è possibile utilizzare più viste per mostrare un insieme di dati in modi diversi.

  • Come accennato all'inizio ci sono altre varianti di architetture MVC. Uno che per esempio ddriver suggerisce come risposta.

+0

È possibile mantenere la vista sull'azione utente del controller usando questo particolare schema? Almeno, 'View' vorrebbe avere pochi richiami. –

+0

@VasiliyStavenko Sì, certo. Usando Qt, nella vista puoi emettere i segnali quando c'è un'azione dell'utente e connetterla nel controller. Per consentire alla vista di richiamare, definire la funzione come uno spazio pubblico e, come prima, eseguire la connessione nel controller. L'importanza è lasciare che i callback siano indipendenti da qualsiasi controller o modello esterno :). –

3

ho intenzione di rispondere a questa nel contesto di Passive-View e Model-View-Presenter

Model View Presenter

che (see Wikipedia)

è una derivazione del Model-View-Controller (MVC) modello architettonico, ed è usato principalmente per costruire interfacce utente.

Il Modello:

modifiche al modello/soggetto deve essere osservabile.La maggior parte dei dettagli Oggetto/Osservatore sono gestiti dal meccanismo segnale/slot, quindi per questo caso di uso semplice è sufficiente rendere osservabile il Modello dandogli un segnale che emette il valore. Poiché i compilatori online non supportano Qt, userò boost :: signals2 e 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_; 
}; 

Il presentatore:

Qui il presentatore è Observer, non la vista. Il lavoro di Presenters è tradurre il valore integrale del modello in una rappresentazione testuale per la visualizzazione. Qui abbiamo davvero due controller, uno per la notazione decimale e uno per la notazione esadecimale. Sebbene possibilmente sovradimensionato per questo semplice caso, creiamo una classe base abstact per 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_; 
}; 

e un'implementazione decimale presentatore

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); 
    } 
}; 

e un'implementazione per il caso esadecimale.

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); 
    } 
}; 

E infine un aggregato Presentatore

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_; 
}; 

The View:

è il solo lavoro visualizza un valore di testo, in modo da poter utilizzare la stessa vista per entrambi i casi.

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_; 
}; 

E la vista aggregata.

class View 
{ 
public: 

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

    TextView hexView; 
    TextView decView; 
}; 

In un'applicazione Qt, ogni vista avrebbe un puntatore alla corrispondente etichetta e sarebbe impostare il testo dell'etichetta.

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

In questo contesto si può anche rispondere a una domanda 1.

1) sarebbe meglio collegare i segnali pulsanti per gli slot del controller direttamente (come nella parte commentata in vista :: setController)?

Dal momento che desideriamo una "Vista passiva" senza logica, è perfettamente possibile connettersi direttamente al controller se i parametri dei controlli sono adatti. Se è necessario convertire, ad esempio una stringa std :: string in una QString, è possibile creare uno slot locale che esegue la conversione e passa il valore o utilizzare lambda per il lavoro in Qt5.

Il controller deve sapere quale vista ha chiamato in modo che possa utilizzare le informazioni corrette dalla vista, non è vero?

No, non è così. Se ha bisogno di fare cose diverse, dovrebbe essere presente un presentatore separato o un presentatore con metodi separati per ogni caso.

2) Qual è il modo ottimale per sapere cosa è cambiato quando viene chiamato l'aggiornamento dal modello? È un passaggio/looping attraverso tutte le possibilità, una mappa predefinita ...?

Il modo ottimale è che il modello comunichi all'osservatore cosa è cambiato. Questo può essere fatto con segnali diversi o con un evento che contiene le informazioni. In questo caso c'è un solo valore, quindi non c'è differenza.

3) Inoltre, devo passare le informazioni necessarie tramite la funzione di aggiornamento, oppure lasciare che View verifichi i valori richiesti del Modello una volta che viene notificato?

Passare le informazioni per evitare calcoli di modifica ridondanti nel presentatore.

4) La vista deve avere un riferimento al modello?

No, almeno non in MVP.

I pezzi possono essere messi insieme in questo modo:

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"); 
} 

che crea il seguente output

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

Live on Coliru

+0

Utilizzo di segnali di boost tipo di sconfitte lo scopo in una domanda su Qt – dtech

+0

@ddriver Non proprio. Non influisce sul design. In realtà è più su segnali/slot che su Qt. – Thomas