27

有人可以简单地解释“信号和插槽”模式吗?

4

6 回答 6

29

信号和槽是一种将发送器(信号)和零个或多个接收器(槽)解耦的方法。假设您有一个系统,其中包含您希望对系统的任何其他对这些事件感兴趣的部分可用的事件。与其将生成事件的代码硬连接到想要了解这些事件的代码,不如使用信号和槽模式。

当发送者发出事件信号时(通常通过调用与该事件/信号相关的函数),该事件的所有接收者都会被自动调用。这允许您在程序的生命周期内根据需要连接和断开接收器。

由于这个问题被标记为 C++,这里有一个指向Boost.Signals库的链接,其中有更详尽的解释。

于 2008-11-23T20:25:00.707 回答
16

我认为当您将信号和插槽视为观察者模式或发布/订阅者模式的可能实现工具时,可以最好地描述它们。有一个signal,例如buttonPressed(IdType)在发布者端。每当按下按钮时,都会调用连接到该信号的所有插槽。插槽位于订阅方一侧。例如,插槽可以是sendMail(IdType).

随着事件“按下按钮”,插槽将知道按下了哪个按钮,因为 id 将被移交。IdType表示通过发布者和订阅者之间的连接发送的数据的类型。订阅者可能的操作是可以与connect(signal, slot)连接,因此如果按下按钮,则调用该特定插槽。buttonPressed(IdType)sendMail(IdType)

这样做的好处是订阅者(插槽端)不需要关心信号的细节。它只需要连接。因此,这里我们有大量的松散耦合。您可以更改按钮的实现,但插槽的界面仍然相同。

查看Qt Signals/SlotsBoost Signals了解更多信息。

于 2008-11-23T20:27:48.140 回答
6

想象一下在您的应用程序中有一个 GUI。大多数时候,控制流不会是非常线性的,也就是说,您需要一个与 GUI 交互的用户(如按钮、菜单等),而不是有一个清晰的动作序列。

这本质上是一个事件驱动模型,可以通过信号和槽模式很好地实现。信号是由对象(想想 GUI 组件)生成的事件,而插槽是这些事件的接收者。

这是一个示例:假设您有一个复选框,在您的编程语言中表示为一个对象。该复选框可能会发生多种情况:它可以被切换,这反过来也意味着它要么被设置,要么被取消。这些是它可以发出的信号。我们将它们命名为 checkboxToggled、checkboxSet 和 checkboxUnset。如您所见,在此示例中,复选框将始终在切换时发出 checkboxToggled 信号,但也恰好是其他两个信号之一,具体取决于状态如何变化。

现在想象有一些其他对象,即一个标签,为了这个例子,它总是作为一个对象存在,但可以“出现”和“消失”,以及一个系统哔声(也由一个对象表示),它可以简单地哔哔声。这些是这些对象所拥有的插槽。我们将它们称为“messageAppear”、“messageDisappear”和“beep”。

假设您希望每次切换复选框时系统发出蜂鸣声,并且标签的出现或消失取决于用户是否选中或清除了复选框。

因此,您可以将以下信号连接到以下插槽(左侧的信号,右侧的插槽):

checkboxToggled -> beep
checkboxSet -> messageAppear
checkboxUnset -> messageDisappear

基本上就是这样。

信号和槽也可能有参数。例如,使用设置数值的滑块,您希望在用户移动滑块后立即将更改后的值与发射的信号一起发送:sliderChanged(int)。

当然,要真正做一些有用的事情,你会编写一些自己的类,其中包含一些自己的信号和插槽。这很容易完成,并且使用这些自己的信号和插槽,您可以以事件驱动的方式与 GUI 或代码的其他部分进行交互。

请记住,信号和时隙通常是对称的,因为通常可能存在与时隙相对应的信号。例如,一个复选框在切换时可能会发出一个信号,但它也可能包含一个切换复选框本身的插槽。分离总是彼此相反设置的复选框很容易实现。

于 2008-11-23T20:30:30.977 回答
4

我假设您在谈论 QT 的信号和插槽。
这很简单。

一个类的实例可以触发一个信号,而另一个类的另一个实例可能会在槽中捕获该信号。这有点像函数调用,只是调用函数的人不需要知道谁想要接收调用。

最好的说明方法是举例。
QPushButton 类有一个信号 QPushButton::clicked()。每当单击按钮时都会触发该信号。按钮不需要知道谁有兴趣知道发生了点击。它只是触发信号,任何有兴趣的人都可以连接到它。
放置按钮的 QDialog 实际上很想知道按钮何时被单击。它有插槽 MyDialog::buttonClicked()。在 MyDialog c'tor 上,您需要将按钮 click() 信号连接到对话框的 buttonClicked() 插槽,以便在触发信号时调用该插槽。

一堆更高级的东西:

  • 参数,信号可以有参数,这些参数也可以选择传递给插槽。
  • 跨线程调用 - 如果您正在建立一个需要跨线程的信号槽连接,那么 QT 将自动缓冲信号并将它们排队到正确的线程。这会自动发生,例如当 GUI 线程需要与工作线程通信时。

这是 QT 文档中的更多信息。

于 2008-11-23T20:26:44.617 回答
1

我为信号和槽找到的最好的例子和解释是这个代码项目文章

于 2013-08-14T11:34:06.893 回答
1

有一个常见的误解,认为类是名词,如 Person、Dog、Bicycle 等。那么认为一个人(实例)有一条狗和一辆自行车是有道理的。

让我们从什么是对象(假设是)开始。对象是数据和过程。什么是程序?数据和程序。对象应该是(相对)“小的”独立子程序。因为 oo 编程的教学非常含糊且被滥用(需要引用),人们认为一切都需要是类或对象。事实并非如此,对象是具有“小”API(公共子例程)的“小”独立程序。一些程序员甚至不将他们的项目分解为子程序,而只是使用数据和过程更合适的对象。

现在,假设我们同意对象是程序,我们可能会同意,在大多数情况下,程序不需要具有类似大小和复杂性的其他程序的副本(即一个对象不是指向另一个对象的指针),它可能需要更小的程序来管理数据(如数据结构),但恕我直言不需要另一个对象。

为什么?因为耦合对象使它们相互依赖。为什么这么糟糕?因为当对象是独立的时,您可以测试它们并且还向其他程序员和客户保证该对象(一个小的独立程序)能够以高确定性执行某些任务。您还可以确保只要没有对该对象进行任何更改,它就会继续执行。

那么什么是插槽和信号呢?如果您了解对象就像程序一样,理想情况下它们不应该保存指向其他对象的副本或指针,那么您需要某种方式让它们进行通信。例如,在您的计算机上运行的进程可以使用套接字、IP 地址和端口进行通信。对象可以使用与RPC非常相似的东西,称为信号和槽。这些是一种数据结构,旨在作为两个较大对象之间的中介,这些对象存储对象的子例程 ( slots) 并允许其他对象使用适当的参数调用 ( signal) 这些子例程 () slots,而不需要知道关于这些其他对象的任何信息,而不知道它们需要哪些参数。

因此,底层结构是(可能)强类型过程指针的集合(可能是数组),其他对象可以使用合适的参数调用,而无需指向这些对象的指针。调用者只需要访问定义预期参数的信号对象(不包含实现细节)。

这也是灵活的,因为它允许一些特殊的用例,比如只响应一次信号的槽,一个信号的多个槽以及其他类似的用例,比如去抖动。

于 2017-05-07T13:41:35.980 回答