插件应该使用什么:钩子,事件或其他东西?


24

考虑一个允许插件对其程序流做出反应的应用。

我知道两种方法可以实现:钩子事件

1.挂钩

在主程序流中使用调用来清空函数。插件可以覆盖这些功能。

例如,Drupal CMS实现了可用于模块和主题的挂钩。这是在file_copy函数中如何实现钩子的示例。

function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
    // ... [File copying routine]

    // Inform modules that the file has been copied.
    module_invoke_all('file_copy', $file, $source);

    return $file;
    // ...
}

模块可以实现modulename_file_copy($file, $source)将由module_invoke_allin 调用的功能file_copy。该功能完成后,file_copy将恢复执行。

2.活动

拥有应用程序分发事件,插件可以监听该事件。收到已订阅的事件后,插件将拦截程序流并执行必要的操作。

例如,一个jQuery画廊插件Fotorama 实现了几个事件。例如,这show是触发fotorama:show事件的方法的一部分。

  that.show = function (options) {
    // ... [show the new frame]

    // [fire the event]
    options.reset || triggerEvent('show', {
      user: options.user,
      time: time
    });

    // ... [do lots of other stuff with navigation bars, etc.]
  };

脚本可以侦听此事件,并在触发时执行以下操作:

$('.fotorama').on(
  'fotorama:show',
  function (e, fotorama, extra) {
    console.log(e.type + (extra.user ? ' after user’s touch' : ''));
    console.log('transition duration: ' + extra.time);
  }
);

  1. 还有其他主流方式来实现这种插件行为吗?

  2. 如果不是,何时应使用钩子,何时应使用事件?从应用程序和插件开发人员的角度来看,考虑最终目标是使代码更具可维护性和可读性吗?

Answers:


17

挂钩和事件之间的主要区别是松耦合与紧耦合。

钩子是广播已发生事件的通用方法。您可以添加新的挂钩,而不必重新编译插件,并且所有挂钩都遵循通用的设计模式。一旦定义了hook API,它就不会更改,因此应用程序和插件之间的耦合不太可能中断。

事件与应用程序紧密相关。事件可以定义附加到事件的参数,如果更改这些参数,则会使用现有插件破坏API。

它们都达到相同的结果。这仅取决于您要如何将插件耦合到应用程序。

Hooks可以为您提供更动态的耦合,当您发布新版本的应用程序时,耦合不会中断,但是缺点是您不会收到任何编译时警告,表明这些插件不再兼容。

事件使您能够获取需要修改插件的编译时错误,因为某些事件签名已更改。

您要求替代方法。

命令:

而不是插件响应触发的事件。插件将命令对象推送到应用程序。每个命令对象都实现命令使用的接口。当应用程序需要执行功能时,它将运行该功能的所有命令。这非常类似于事件,除了它是作为对象而不是回调函数实现的。

巨集:

事情发生时,插件无需响应。插件会主动导致事情发生。宏是一种在应用程序上方运行的小型高级语言,告诉它该怎么做。

状态更改侦听器:

事件由应用程序在开发人员的预见下触发。开发人员必须有意识地编写发布事件的代码。相反,另一种方法是使对象的内部状态更改时自动广播。更改属性或其他指标。然后,插件可以侦听这些特定的状态更改并做出相应的反应。这种方法的优点是程序员不必记住广播事件。例如,可能有一个Document对象,程序员设置了一个标志来标记需要保存该文档。此状态更改将广播给正在监听的插件,并且可能有一个插件将文档标题更改为包括星号。


2
+1表示替代方案,-1表示定义和耦合参数(的确存在,但是耦合是设计选择的结果,无论您给插件系统指定的名称)

5
我认为您也在假设事件是如何从生成器传播到观察者/听众的。事实上,这是相反的,钩子是紧密耦合的,而事件不是。
艾哈迈德·马苏德

3

绝对是事件,它允许在体系结构级别上进行必要的抽象。

不要指望任何写插件的人确实按照文档记载或以任何正确的方式这样做。我一直在维护着一个记录良好的API,拥有数百万的用户,并且我可以从非常痛苦的经历中告诉您,基本上没有人会阅读该文档,并且几乎没有人正确地使用该API。

以带有钩子的以下示例为例:您有一个正在运行20个插件的系统。这些插件file_copy之一以记录方式调用该方法,并期望记录结果。但是其他一些插件已经钩住了该功能,因此以下问题之一会导致崩溃或故障:

  • 挂钩函数只会崩溃。所有其他插件现在都已拧紧,因为它们不再可以file_copy或该函数的工作方式与预期的不同。
  • 根据文档,输入是正确的,但另一个插件不期望输入,并且会产生奇怪的结果或崩溃。
  • 调用运行良好,但是结果不再是根据文档所述的预期,因此插件失败或崩溃。

如果您对那些插件中具有相同问题的事件执行与上述相同的操作,则会发生以下情况:

  • 插件X的事件功能崩溃,但其他所有功能正常工作。但是由于这些插件无关,因此您可以在其他插件继续正常工作的情况下直接禁用崩溃的插件。
  • 奇怪的输入可以由您的函数正确处理,并且您可以分别检查每个插件的所有可能情况。插件开发人员现在拥有一种稳定且可靠的方式来实际测试他的插件,这使他可以确保如果对他有用,它将对每个人都有效。如果一个插件提供了错误的输入,则可以将其隔离到那个插件。
  • 同样,在所有情况下都可以正确检查和定义结果,因此插件开发人员可以从该功能中获得稳定可靠的答案,他/她可以对其进行测试。

1

继承可能是一个选择。

除了钩子之外,继承不需要额外的方法定义,并且在没有任何钩子的情况下调用空方法不会造成性能损失。

除了事件之外,继承也不需要用于事件调用的额外代码。

但是,如果只有一个插件可以修改一种行为,则继承效果最好。如果您需要许多插件,则第二个插件需要从第一个插件等派生,这是不合适的。


-1因为您使用继承,然后更改实例化代码以使用您的规范并滥用继承,因为新行为作为主要应用程序具有不同的用途……
SparK 2014年

0

绝对是事件。它使您的体系结构具有更大的可扩展性。

想象一下,如果您需要将插件放置在单独的计算机上,将会发生什么情况。使用事件-您只需要修改少量代码即可使事件基于网络。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.