首页 文章

直接呼叫功能与发射信号(Qt - 信号和插槽)

提问于
浏览
2

在这一点上,我处于一个两难境地,即何时发出信号与直接调用另一个类中的方法(相同的线程) . 例如,在我正在做的教程中,我正在将Instrument类(Model)的NotifyConnected信号连接到'this'又名View Manager的onConnected插槽,请参阅SetupViewManager :: WireButtons(),代码中的第三行 . (我正在使用MVVM设计模式) . 这里的信号和插槽很有意义,因为Instruments类(Model)不应该对View Manager有任何了解 . (即将视图管理器的引用传递给模型是否定的,因为它会打破MVVM设计模式 . )很棒 .

我的问题是,在教程中接下来,ViewManager的onConnected插槽然后发出其他信号,然后我必须继续手动连接到另一个View类的插槽,即SetupTab(ref void SetupViewManager :: onConnected和void SetupViewManager ::代码中的WireDisplayUpdate()) .

我的问题是,为什么不直接调用SetupTab中的方法替换onConnected插槽中的所有发出?感觉像是过于复杂的代码 .

为了简单地从我引用的另一个类中调用一个公共函数(信号),为了发出信号而不得不连接所有内容,有什么好处呢?它不是一个多线程应用程序(我知道信号和插槽是线程安全的) .

请赐教 .

谢谢 .

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

2 回答

  • 2

    信号槽机制的优点:

    当你的 class 没有关于它的客户信息时,

    • 易于使用;

    • 可用于线程安全调用;

    • 你不能手动记住所有通知它们的对象;

    • 连接两个对象的唯一规则是它们都必须是QObject子类 .

    缺点:

    • 较慢的呼叫(每个信号发出所有连接对象的扫描列表);

    • 可能复杂的意大利面条代码;你不知道,谁和什么时候会打电话给任何一个插槽或者谁会发出信号 .

    你应该考虑一下自己的情况 . 如果SetupViewManager外没有信号“侦听器”,请尝试直接调用 . 如果其他人可以连接到此信号,您的选择就是发出它们 .

    使用信号也可能有其他原因 . 但没有理由只使用它们来调用函数 . 在一个线程中,至少 .

  • 2

    信号和插槽用于解耦类,因此他们无需明确知道谁使用了他们的功能以及如何使用 . 在许多情况下,解耦是软件设计的理想特征 . 当然,它本身并不是目的,它可以帮助您推断代码的正确性并使其更易于维护 . 解耦有助于理解/推理代码,因为它会导致您可以单独分析的较小代码单元 . 另一种看待它的方法是关注点的分离:让一个代码单元做一件事,例如:将一个课程集中在功能的一个方面 .

    当你有一对类并希望决定是否结合它们时,请考虑它们是否可以与其他类一起使用 . A 可以耦合到 B ,但耦合该对的接口是否可以被 C 而不是 B 使用?如果是这样,那么必须使用一些去耦模式,并且信号槽模式就是其中之一 .

    例如,让我们比较这两个接口如何影响与用户代码的耦合 . 目标很简单:将调试输出添加到对象的析构函数:

    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 pattern的一个特别灵活的实现 . 这将您的代码与对象的代码分离 .

    第二个实现,使用模板方法相似的模式,强制更紧密的耦合:要对_321637的破坏采取行动,您必须具有实现所需功能的派生类的实例 .

相关问题