【问题标题】:Call function directly vs emiting Signal (Qt - Signals and Slots)直接调用函数与发出信号(Qt - 信号和插槽)
【发布时间】:2016-10-21 12:28:04
【问题描述】:

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

我遇到的问题是,接下来在教程中,ViewManager 的 onConnected 插槽会发出其他信号,然后我必须继续手动连接到另一个 View 类的插槽,即 SetupTab(参考 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);
    }
}

【问题讨论】:

  • 你能改变你的文本格式,让眼睛更容易阅读吗?另外,一段代码可能会更清楚地说明问题。
  • 您是否将视图设置为ViewManger?你不应该在那里建立这些联系吗?很难说我们什么时候对这些课程一无所知。
  • 按要求完成。请参考代码。谢谢!
  • 好吧,在这种情况下,它几乎没有任何区别。您可以从外部连接到这些信号,但如果您不希望发生这种情况,也许最好直接调用m_setupTab 的方法。
  • 所以你的意思是,使用 m_setupTab.functionName(param) 直接在 SetupViewManager::onConnected() 中调用 Setuptab 中的方法是正确的,而不是发出信号然后连接信号/插槽。对吗?

标签: c++ qt signals-slots


【解决方案1】:

信号和槽用于解耦类,这样它们就不需要明确知道谁使用它们的功能以及如何使用它们。在许多情况下,解耦是软件设计的理想特征。当然,它本身并不是目的,它在帮助您推理代码的正确性并使其更易于维护时很有用。解耦有助于理解/推理代码,因为它会导致您可以单独分析的更小的代码单元。另一种看待它的方式是关注点分离:让一个代码单元做一件事,例如专注于某一方面的功能。

当您有一对类并希望决定是否将它们耦合时,请考虑它们是否可以与其他类一起使用。 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 的一个特别灵活的实现。这将您的代码与对象的代码分离。

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

【讨论】:

  • 很好的答案。干杯:)
【解决方案2】:

信号槽机制的优点:

  • 在您的班级没有关于其客户的信息时易于使用;
  • 可用于线程安全调用;
  • 您不能手动记住所有对象以通知它们;
  • 连接两个对象的唯一规则是它们都必须是 QObject 子类。

缺点:

  • 较慢的调用(每个信号发出扫描所有连接对象的列表);
  • 可能是复杂的意大利面条代码;你不知道谁会在什么时候调用任何插槽或者谁会得到发射信号。

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

使用信号可能还有其他原因。但是没有理由仅仅为了调用一个函数而使用它们。至少在一个线程中。

【讨论】:

    猜你喜欢
    • 2014-03-20
    • 2012-10-15
    • 1970-01-01
    • 2016-11-10
    • 1970-01-01
    • 2015-03-14
    • 1970-01-01
    • 1970-01-01
    • 2011-04-08
    相关资源
    最近更新 更多