Que doivent utiliser les plugins: hooks, événements ou autre chose?

24

Prenons une application qui permet aux plugins de réagir à son flux de programmes.

Je connais 2 façons d'y parvenir: les crochets et les événements

1. Crochets

Utilisez des appels pour vider des fonctions dans le flux de programme principal. Ces fonctions peuvent être remplacées par des plugins.

Par exemple, Drupal CMS implémente des hooks disponibles pour les modules et les thèmes. Voici un exemple de la façon dont le hook est implémenté dans une fonction 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;
    // ...
}

Un module peut implémenter une modulename_file_copy($file, $source)fonction qui sera appelée par le module_invoke_allin file_copy. Une fois cette fonction terminée, l' file_copyexécution reprendra.

2. Événements

Avoir les événements de répartition de l'application, qui peuvent être écoutés par les plugins. Après avoir reçu un événement auquel il a été abonné, un plugin interceptera le flux du programme et effectuera les opérations nécessaires.

Par exemple, un plugin de galerie jQuery Fotorama implémente plusieurs événements . À titre d'exemple, voici une partie de sa showméthode qui déclenche l' fotorama:showévénement.

  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.]
  };

Un script peut écouter cet événement et faire quelque chose lorsqu'il se déclenche:

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

QUESTION

  1. Existe-t-il d'autres moyens courants pour implémenter un tel comportement de plugin?

  2. Sinon, quand doit-on utiliser des hooks et quand doit-on utiliser des événements? Considérant que le but ultime est de rendre le code plus maintenable et lisible, du point de vue de l'application et du développeur du plugin?

sbichenko
la source

Réponses:

17

La principale différence entre un crochet et un événement est le couplage lâche par rapport au couplage serré.

Un crochet est un moyen générique de diffuser que quelque chose s'est produit. Vous pouvez ajouter de nouveaux hooks sans avoir à recompiler les plugins, et tous les hooks suivent un modèle de conception générique. Une fois que l'API hook est définie, elle ne change pas, donc le couplage entre l'application et le plugin ne risque pas de se rompre.

Les événements sont plus étroitement liés à l'application. Les événements peuvent définir des paramètres qui sont attachés à l'événement, et si vous modifiez ces paramètres, vous cassez l'API avec les plugins existants.

Ils obtiennent tous deux les mêmes résultats. Cela dépend simplement de la façon dont vous souhaitez coupler le plugin à l'application.

Les hooks peuvent vous offrir un couplage plus dynamique qui ne risque pas de se casser à mesure que de nouvelles versions de votre application sont publiées, mais l'inconvénient est que vous ne recevez aucun avertissement de temps de compilation que les plugins ne sont plus compatibles.

Les événements vous offrent la possibilité d'obtenir des erreurs de temps de compilation dont le plugin a besoin d'être modifié, car certaines signatures d'événement ont changé.

Vous avez demandé des approches alternatives.

Commandes:

Au lieu de plugins répondant aux événements déclenchés. Les plugins envoient des objets de commande à l'application. Chaque objet de commande implémente une interface utilisée par les commandes. Lorsque l'application doit exécuter une fonctionnalité, elle exécute toutes les commandes de cette fonctionnalité. Cela ressemble beaucoup aux événements, sauf qu'il est implémenté en tant qu'objets au lieu de fonctions de rappel.

Macros:

Au lieu que les plugins répondent aux événements. Les plugins provoquent de manière proactive des choses. Une macro est un petit langage de haut niveau qui s'exécute au-dessus de l'application pour lui dire quoi faire.

Auditeurs de changement d'état:

Les événements sont déclenchés par l'application avec une réflexion préalable du développeur. Le développeur doit écrire sciemment le code qui émet l'événement. Au lieu de cela, une approche alternative consiste à rendre les objets diffusés automatiquement lorsque leur état interne a changé. Soit le changement d'une propriété ou d'autres indicateurs. Les plugins peuvent alors écouter ces changements d'état spécifiques et réagir en conséquence. L'avantage de cette approche est que le programmeur n'a pas à se rappeler de diffuser des événements. Par exemple, il peut y avoir un objet Document et le programmeur définit un indicateur pour indiquer que le document doit être enregistré. Ce changement d'état est diffusé aux plugins d'écoute, et il peut y avoir un plugin qui modifie le titre du document pour inclure un astérisque.

Reactgular
la source
2
+1 pour les alternatives, -1 pour les définitions et l'argument de couplage (qui fait exist mais le couplage est une conséquence des choix de conception, quel que soit le nom que vous donnez à votre système de plug - in)
5
Je pense que vous faites également des hypothèses sur la façon dont un événement se déplace du générateur à l'observateur / auditeur. En fait, c'est l'inverse, les crochets sont étroitement couplés alors que les événements ne le sont pas.
Ahmed Masud
3

Certainement événementiel, il permet déjà l'abstraction nécessaire au niveau architectural.

Ne vous attendez pas à ce que quiconque rédige un plug-in le fasse réellement comme documenté ou de quelque manière que ce soit. J'ai maintenu une API bien documentée avec des millions d'utilisateurs et je peux vous dire d'une expérience très pénible que personne ne lit la documentation et presque personne n'utilise l'API correctement.

Prenons l'exemple suivant avec les hooks: Vous avez un système sur lequel 20 plugins sont en cours d'exécution. L'un de ces plugins appelle la file_copyméthode dans la manière dont elle est documentée et attend un résultat tel que documenté. Mais un autre plugin a accroché cette fonction et donc l'un des problèmes suivants provoque un crash ou un dysfonctionnement:

  • La fonction crochet se bloque simplement. Tous les autres plugins sont vissés maintenant, car ils ne peuvent plus file_copy ou la fonction fonctionne différemment de ce qui est attendu.
  • L'entrée est correcte sur la base de la documentation, mais l'autre plugin ne s'y attend pas et fournit des résultats étranges ou des plantages.
  • L'appel fonctionne correctement, mais le résultat n'est plus celui attendu selon la documentation, le plugin échoue ou se bloque.

Si vous faites la même chose que ci-dessus avec des événements avec les mêmes problèmes dans ces plugins, les événements suivants se produisent:

  • La fonction événementielle du plugin X se bloque, mais toutes les autres fonctionnent correctement. Mais comme ces plugins ne sont pas liés, vous pouvez simplement désactiver ce plugin en panne pendant que les autres continuent de fonctionner correctement.
  • L'entrée étrange peut être correctement gérée par votre fonction et vous pouvez vérifier correctement toutes les choses possibles pour chaque plugin individuellement. Le développeur du plugin a maintenant un moyen stable et fiable de tester son plugin, ce qui lui permet d'être sûr que si cela fonctionne pour lui, cela fonctionnera pour tout le monde. Si un plugin fournit une entrée incorrecte, il peut être isolé de ce plugin.
  • De même, le résultat peut être vérifié et défini correctement en toutes circonstances, de sorte que le développeur du plugin a une réponse stable et fiable de cette fonction qu'il / elle peut tester.
TwoThe
la source
1

L'héritage peut être une option.

Hormis les hooks, l'héritage n'a pas besoin de définitions de méthode supplémentaires et il n'y a aucune perte de performances pour appeler la méthode vide au cas où il n'y aurait rien de connecté.

Outre les événements, l'héritage n'a également pas besoin de code supplémentaire pour l'invocation d'événements.

Cependant, l'héritage fonctionne mieux s'il n'y a qu'un seul plugin modifiant un type de comportement. Si vous avez besoin de nombreux plugins, le second devra dériver du premier, etc., ce qui n'est pas approprié.

Thomas Weller
la source
-1 Parce que vous utilisez l'héritage et que vous modifiez ensuite le code d'instanciation pour utiliser vos spécifications et abuser de l'héritage car le nouveau comportement a un objectif différent en tant qu'application principale ...
SparK
0

Certainement des événements. Il permet à votre architecture d'être plus évolutive.

Imaginez ce qui se passera si vous devez par exemple placer votre plugin sur une machine distincte. Utilisation d'événements - vous aurez juste besoin de modifier une petite paix de code pour rendre votre réseau d'événements basé.

vpol
la source