2016-06-20 5 views
2

An diesem Punkt bin ich in einem Dilemma, wann ein Signal vs Aufruf einer Methode in einer anderen Klasse direkt (gleicher Thread). Zum Beispiel verbinde ich im Tutorial das NotifyConnected-Signal der Instrument-Klasse (Model) mit dem onConnected-Slot von 'this' aka The View Manager, siehe SetupViewManager :: WireButtons(), dritte Zeile im Code . (Ich benutze MVVM Design Pattern). Hier sind Signale und Slots sinnvoll, da die Instrumentenklasse (Model) nichts über den View Manager wissen sollte. (d. h. das Übergeben einer Referenz des Ansichtsverwalters an das Modell ist ein Nein, da dies das MVVM-Entwurfsmuster durchbrechen würde.) Brilliant.Aufruf Funktion direkt vs emittierenden Signal (Qt - Signale und Slots)

Das Problem, das ich habe, ist, dass als nächstes im Tutorial der OnConnected Slot des ViewManagers dann andere Signale aussendet, die ich dann manuell mit den Slots einer anderen View Klasse verbinden muss, zB SetupTab (ref void SetupViewManager :: onConnected und void SetupViewManager :: WireDisplayUpdate() im Code).

Meine Frage ist, warum nicht einfach ersetzen alle emittiert in der onConnected Slot mit dem Aufruf der Methoden direkt von SetupTab? Fühlt sich wie überkomplizierter Code für mich an.

Was ist der Vorteil, die extra Meile zu gehen, Signale auszusenden und alles zu verkabeln, nur um eine öffentliche Funktion (Signal) von einer anderen Klasse aufzurufen, für die ich eine Referenz habe? Es ist keine Multithread-Anwendung (Ich weiß, dass Signale und Slots Thread-sicher sind).

Bitte erleuchten Sie mich.

Danke.

setupviewmanager.cpp:

#include "setupviewmanager.h" 
#include "View/setuptab.h" 
#include "Model/instrument.h" 
#include "Model/settings.h" 
#include "utils.h" 

namespace Ps 
{ 
    SetupViewManager::SetupViewManager(QObject *parent, 
             SetupTab &tab, 
             Instrument &inst, 
             Settings &config) : 
     QObject(parent), 
     m_setupTab(tab), 
     m_instrument(inst) 
    { 
     WireSettings(config); 
     config.ParseJsonData(); 
     WireHostAndPort(); 
     WireMessages(); 
     WireButtons(); 
     WireDisplayUpdate(); 

     m_setupTab.SetHostName(config.getHostName()); 
     m_setupTab.SetPort(config.getPortNumber()); 
     m_setupTab.SetCommands(config.getCommandsAsModel()); 
     auto long_wait = config.getLongWaitMs(); 
     auto short_wait = config.getShortWaitMs(); 
     m_instrument.SetlongWaitMs(long_wait); 
     m_instrument.SetShortWaitMs(short_wait); 
     emit NotifyStatusUpdated(tr("Long wait Ms: %1").arg(long_wait)); 
     emit NotifyStatusUpdated(tr("Short Wait Ms: %1").arg(short_wait)); 
     onDisconnected(); 
    } 

    SetupViewManager::~SetupViewManager() 
    { 
     Utils::DestructorMsg(this); 
    } 

    void SetupViewManager::WireSettings(Settings &config) 
    { 
     connect(&config, &Settings::NotifyStatusMessage, &m_setupTab, &SetupTab::onStatusUpdated); 
    } 

    void SetupViewManager::WireHostAndPort() 
    { 
     connect(&m_setupTab, &SetupTab::NotifyHostNameChanged, &m_instrument, &Instrument::onHostNameChanged); 
     connect(&m_setupTab, &SetupTab::NotifyPortChanged, &m_instrument, &Instrument::onPortChanged); 
    } 

    void SetupViewManager::WireMessages() 
    { 
     connect(&m_instrument, &Instrument::NotifyErrorDetected, &m_setupTab, &SetupTab::onStatusUpdated); 
     connect(&m_instrument, &Instrument::NotifyStatusUpdated, &m_setupTab, &SetupTab::onStatusUpdated); 
     connect(this, &SetupViewManager::NotifyStatusUpdated, &m_setupTab, &SetupTab::onStatusUpdated); 
    } 

    void SetupViewManager::WireButtons() 
    { 
     connect(&m_setupTab, &SetupTab::NotifyConnectClicked,&m_instrument, &Instrument::Connect); 
     connect(&m_instrument, &Instrument::NotifyConnected, &m_setupTab, &SetupTab::onConnected); 
     connect(&m_instrument, &Instrument::NotifyConnected, this, &SetupViewManager::onConnected); 

     connect(&m_setupTab, &SetupTab::NotifyDisconnectClicked,&m_instrument, &Instrument::Disconnect); 
     connect(&m_instrument, &Instrument::NotifyDisconnected, &m_setupTab,&SetupTab::onDisconnected); 
     connect(&m_instrument, &Instrument::NotifyDisconnected, this, &SetupViewManager::onDisconnected); 

     connect(&m_setupTab, &SetupTab::NotifySendClicked,&m_instrument, &Instrument::onSendRequest); 
     connect(&m_instrument, &Instrument::NotifyDataSent,&m_setupTab, &SetupTab::onDataSent); 

     connect(&m_setupTab, &SetupTab::NotifyReceiveClicked,&m_instrument, &Instrument::onReceiveRequest); 
     connect(&m_instrument, &Instrument::NotifyDataReceived,&m_setupTab, &SetupTab::onDataReceived); 
    } 

    void SetupViewManager::WireDisplayUpdate() 
    { 
     connect (this, &SetupViewManager::NotifyConnectEnabled, &m_setupTab, &SetupTab::onConnectEnabled); 
     connect (this, &SetupViewManager::NotifyDisconnectEnabled, &m_setupTab, &SetupTab::onDisconnectEnabled); 
     connect (this, &SetupViewManager::NotifyDirectCommandsEnabled, &m_setupTab, &SetupTab::onDirectCommandsEnabled); 
     connect (this, &SetupViewManager::NotifyControlTabEnabled, &m_setupTab, &SetupTab::onControlTabEnabled); 
    } 

    void SetupViewManager::onConnected() 
    { 
     emit NotifyConnectEnabled(false); // HERE. Why not just call method directly with m_setupTab.onConnectEnabled(false); etc...? 
     emit NotifyDisconnectEnabled(true); 
     emit NotifyDirectCommandsEnabled(true); 
     emit NotifyControlTabEnabled(true); 
    } 

    void SetupViewManager::onDisconnected() 
    { 
     emit NotifyConnectEnabled(true); 
     emit NotifyDisconnectEnabled(false); 
     emit NotifyDirectCommandsEnabled(false); 
     emit NotifyControlTabEnabled(false); 
    } 
} 
+0

Können Sie Ihr Textformat ändern, damit es für das Auge leichter zu lesen ist? Auch ein Code könnte die Frage noch etwas klären. –

+0

Stellen Sie eine Ansicht auf einen 'ViewManger' ein? Sollten Sie diese Verbindungen nicht dort machen? Es ist sehr schwer zu sagen, wann wir nichts über diese Klassen wissen. – thuga

+0

Fertig wie gewünscht. Bitte beziehen Sie sich auf den Code. Vielen Dank! – Nokiaowner

Antwort

1

Signale und Slots werden verwendet, um Klassen zu entkoppeln, sodass sie nicht explizit wissen müssen, wer ihre Funktionalität verwendet und wie. In vielen Fällen ist die Entkopplung ein wünschenswertes Merkmal für das Design Ihrer Software. Natürlich ist es kein Selbstzweck, es ist nützlich, wenn es Ihnen hilft, über die Korrektheit des Codes nachzudenken und es leichter zu pflegen.Entkopplung hilft beim Verständnis/Nachdenken über den Code, da er zu kleineren Codeeinheiten führt, die Sie isoliert analysieren können.

Wenn Sie ein Paar Klassen haben und entscheiden möchten, ob Sie sie koppeln sollen oder nicht, überlegen Sie, ob sie mit anderen Klassen verwendet werden können. A könnte mit B gekoppelt werden, aber könnte die Schnittstelle, die das Paar verbindet, von C anstelle von B verwendet werden? Wenn dies der Fall ist, müssen einige Entkopplungsmuster verwendet werden, und das Signal-Slot-Muster ist eines davon.

Lassen Sie uns zum Beispiel vergleichen, wie diese beiden Schnittstellen die Kopplung mit Benutzercode beeinflussen. Das Ziel ist einfach: Debug-Ausgabe auf ein destructor Objekt hinzufügen:

class QObject { 
    ... 
    Q_SIGNAL void destroyed(QObject * obj = Q_NULLPTR); 
}; 

class QObjectB { 
    ... 
    virtual void on_destroyed(); 
}; 

int main() { 
    QObject a; 
    struct ObjectB : QObjectB { 
    void on_destroyed() override { qDebug() << "~QObjectB"; } 
    } b; 
    QObject::connect(&a, &QObject::on_destroyed, []{ qDebug() << "~QObject"; }); 
} 

Die Signal-Slot-Schnittstelle Sie die Funktionalität von vorhandenen Objekten, ohne die Notwendigkeit leicht hinzufügen können sie Unterklasse. Es ist eine besonders flexible Implementierung des Observer-Patterns. Dies entkoppelt Ihren Code vom Code der Objekte.

Die zweite Implementierung, die das Template-ähnliche Muster verwendet, erzwingt eine engere Kopplung: Um die Zerstörung von ObjectB zu bewirken, müssen Sie eine Instanz einer abgeleiteten Klasse haben, in der Sie die gewünschte Funktionalität implementieren.

+0

Große Antwort. Prost :) – Nokiaowner

2

Vorteile des Signal-Slot-Mechanismus:

  • einfach zu bedienen, wenn Sie Ihre Klasse über keine Informationen darüber Kunden sind;
  • kann für Thread-sichere Anrufe verwendet werden;
  • Sie müssen sich nicht alle Objekte manuell merken, um sie zu benachrichtigen;
  • Die einzige Regel, um zwei Objekte zu verbinden, ist, dass sie beide QObject-Unterklassen sein müssen.

Nachteile:

  • langsamer Anruf (jedes Signal auszusenden Scans Liste aller angeschlossenen Objekte);
  • möglicherweise komplizierter Spaghetti-Code; Sie wissen nicht, wer und wann wird einen Schlitz anrufen oder wer wird ausgesendet Signal.

Sie sollten sich über Ihren Fall Gedanken machen. Wenn es keine "Listener" -Signale außerhalb des SetupViewManagers gibt, versuchen Sie direkte Anrufe. Wenn sich jemand anderes mit diesen Signalen verbinden kann, sendet Ihre Wahl diese aus.

Es kann auch andere Gründe geben, Signale zu verwenden. Aber es gibt keinen Grund, sie nur zum Aufruf einer Funktion zu verwenden. Zumindest in einem Thread.