此时我处于一个两难的状态,即什么时候发出信号直接调用另一个类中的方法(相同的线程)。例如,在本教程中,我将仪器类(型号)的NotifyConnected信号连接到'这个'的onConnected插槽。又名视图管理器,请参阅SetupViewManager :: WireButtons(),代码中的第三行。 (我使用MVVM设计模式)。这里的信号和插槽是有意义的,因为Instruments类(Model)不应该对View Manager有任何了解。 (即将视图管理器的引用传递给模型是否定的,因为它会打破MVVM设计模式。)很棒。
我遇到的问题是,在教程中接下来,ViewManager的onConnected插槽然后发出其他信号,然后我必须继续手动连接到另一个View类的插槽,即SetupTab(ref void SetupViewManager :: onConnected and 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);
}
}
答案 0 :(得分:2)
信号槽机制的优点:
缺点:
你应该自己考虑一下你的情况。如果SetupViewManager外没有“侦听器”信号,请尝试直接调用。如果其他人可以连接到这个信号,你的选择是发射它们。
使用信号可能还有其他原因。但是没有理由只使用它们来调用函数。在一个主题中,至少。
答案 1 :(得分: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的一个特别灵活的实现。这将您的代码与对象的代码分离。
使用模板方法相似模式的第二个实现强制更紧密耦合:要对ObjectB
的破坏采取行动,您必须拥有实现所需功能的派生类的实例。