2016-06-20 5 views
2

이 시점에서 나는 다른 클래스의 메서드를 직접 호출 (동일한 스레드)하는 신호를 내보내는 딜레마에 빠졌습니다. 예를 들어 튜토리얼에서 Instrument 클래스 (Model)의 NotifyConnected 신호를 'this'의 뷰 연결 관리자 (onConnected slot)에 연결하고 있습니다. SetupViewManager :: WireButtons(), 코드의 세 번째 줄을 참조하십시오. . MVVM 디자인 패턴을 사용하고 있습니다. Instruments 클래스 (Model)가 View Manager에 대해 알지 않아야하므로 신호와 슬롯이 의미가 있습니다. (즉,보기 관리자의 참조를 모델로 전달하면 MVVM 디자인 패턴을 깨뜨릴 수 있으므로 아무런 문제가되지 않습니다.) 훌륭합니다.호출 함수 직접 대 방출 신호 (Qt - 신호 및 슬롯)

내가 가지고있는 문제는 ViewManager의 onConnected 슬롯이 다른 신호를 내 보낸다는 것입니다. 그러면 수동으로 다른 뷰 클래스, 즉 SetupTab (ref void SetupViewManager :: onConnected)의 슬롯에 연결해야합니다. void SetupViewManager :: WireDisplayUpdate() 코드에서).

제 질문은 onConnected 슬롯의 모든 방사를 SetupTab의 메서드를 직접 호출하는 것으로 바꾸지 않는 이유는 무엇입니까? 나에게 코드를 복잡하게 만드는 느낌.

신호를 내보내고 참조를 가진 다른 클래스의 공용 함수 (신호)를 호출하기 위해 모든 것을 연결해야하는 이점은 무엇입니까? 다중 스레드 응용 프로그램이 아닙니다 (신호와 슬롯은 스레드로부터 안전합니다).

저를 계몽하십시오.

감사합니다.

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

눈을 쉽게 읽을 수 있도록 텍스트 형식을 변경할 수 있습니까? 또한, 코드 조각이 질문을 좀더 명확히 할 수도 있습니다. –

+0

'ViewManger'에보기를 설정합니까? 이 연결을해서는 안됩니까? 우리가 이러한 수업에 대해 모를 때를 말하기는 매우 어렵습니다. – thuga

+0

요청한대로 완료합니다. 친절하게 코드를 참조하십시오. 고맙습니다! – Nokiaowner

답변

1

신호와 슬롯은 클래스를 분리하여 누가 누가 그 기능을 사용하는지 명시 적으로 알 필요가 없도록합니다. 대부분의 경우 디커플링은 소프트웨어 설계의 바람직한 특징입니다. 물론 그것은 그 자체가 목적이 아니며, 코드의 정확성에 대해 추론하는 데 도움이되고 유지 보수가 더 쉬울 때 유용합니다.디커플링은 코드를 이해/추론하는 데 도움이되므로 코드 단위가 작아 지므로 별도로 분석 할 수 있습니다.

한 쌍의 클래스가 있고이를 결합할지 여부를 결정할 때 다른 클래스와 함께 사용할 수 있는지 생각해보십시오. AB에 연결할 수 있지만 B 대신 C에 의해 쌍이 연결된 인터페이스를 사용할 수 있습니까? 그렇다면 일부 디커플링 패턴을 사용해야하며 신호 슬롯 패턴이 그 중 하나입니다.

예를 들어이 두 인터페이스가 사용자 코드와 어떻게 연결되는지 비교해 봅시다. 목적은 간단하다 : 객체의 소멸자에 디버그 출력을 추가

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

신호 슬롯 인터페이스를 사용하면 쉽게 서브 클래 싱 할 필요없이 기존 개체에 기능을 추가 할 수 있습니다. 이것은 Observer 패턴의 특히 유연한 구현입니다. 이렇게하면 코드가 객체의 코드와 분리됩니다.

템플릿 메서드 lookalike 패턴을 사용하는 두 번째 구현은 가까운 결합을 강제합니다. 즉, ObjectB의 파괴에 영향을 주려면 원하는 기능을 구현하는 파생 클래스의 인스턴스가 있어야합니다.

+0

좋은 답변입니다. 건배 :) – Nokiaowner

2

장점 :

  • 는 고객의에 대한 클래스가 아무런 정보가 없을 때 사용하기 쉬운;
  • 은 스레드 안전 호출에 사용될 수 있습니다.
  • 모든 개체를 수동으로 기억하여 알려주지 않아야합니다.
  • 두 객체를 연결하는 유일한 규칙은 모두 QObject 하위 클래스 여야한다는 것입니다.

단점 :

  • 느린 호 (각각의 신호는 스캔 모든 연결된 객체 목록을 방출);
  • 복잡한 스파게티 코드; 당신은 누구와 언제 어떤 슬롯을 부를 것인지 또는 방출 된 신호를 누가 알 것인지 모릅니다.

자신의 사례에 대해 생각해야합니다. SetupViewManager 외부에 "청취자"신호가 없으면 직접 호출을 시도하십시오. 다른 사람이이 신호에 연결할 수있는 경우 선택하면 신호가 방출됩니다.

신호를 사용하는 다른 이유가있을 수도 있습니다. 그러나 함수를 호출하는 데 사용할 이유가 없습니다. 하나의 스레드에서 적어도.