Wann sollte ich die ereignisbasierte Programmierung verwenden?


65

Ich habe Rückrufe übergeben oder nur die Funktionen einer anderen Funktion in meinen Programmen ausgelöst, um die Ausführung von Aufgaben zu ermöglichen. Wenn etwas fertig ist, starte ich die Funktion direkt:

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    shovelSnow();
}

Aber ich habe über viele verschiedene Strategien in der Programmierung gelesen, und eine, von der ich verstehe, dass sie mächtig ist, die ich aber noch nicht praktiziert habe, ist ereignisbasiert (ich glaube, eine Methode, über die ich gelesen habe, hieß "pub-sub" ):

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    $(document).trigger('snow');
}

$(document).bind('snow', shovelSnow);

Ich möchte die objektiven Stärken und Schwächen der ereignisbasierten Programmierung verstehen, anstatt einfach alle Ihre Funktionen aus anderen Funktionen heraus aufzurufen. In welchen Programmiersituationen ist ereignisbasiertes Programmieren sinnvoll?


2
Abgesehen davon können Sie nur verwenden $(document).bind('snow', shovelShow). Sie müssen es nicht in eine anonyme Funktion einschließen.
Karl Bielefeldt

4
Vielleicht möchten Sie auch etwas über "reaktives Programmieren" lernen, was viel mit ereignisgesteuertem Programmieren zu tun hat.
Eric Lippert

Antworten:


75

Ein Ereignis ist eine Benachrichtigung, die ein Ereignis aus der jüngsten Vergangenheit beschreibt.

Eine typische Implementierung eines ereignisgesteuerten Systems verwendet Event-Dispatcher- und Handler-Funktionen (oder Abonnenten ). Der Dispatcher bietet eine API, um Handler mit Ereignissen zu verbinden (jQuery bind), und eine Methode, um ein Ereignis für seine Abonnenten zu veröffentlichen ( triggerin jQuery). Wenn es sich um E / A- oder UI-Ereignisse handelt, gibt es normalerweise auch eine Ereignisschleife , die neue Ereignisse wie Mausklicks erkennt und an den Dispatcher weiterleitet. In JS-Land werden der Dispatcher und die Ereignisschleife vom Browser bereitgestellt.

Bei Code, der direkt mit dem Benutzer interagiert und auf Tastendruck und Klicken reagiert, ist eine ereignisgesteuerte Programmierung (oder eine Variation davon, z. B. eine funktionale reaktive Programmierung ) fast unvermeidbar. Sie, der Programmierer, haben keine Ahnung, wann oder wo der Benutzer klicken wird. Es liegt also am GUI-Framework oder Browser, die Aktion des Benutzers in seiner Ereignisschleife zu erkennen und Ihren Code zu benachrichtigen. Diese Art von Infrastruktur wird auch in Netzwerkanwendungen verwendet (siehe NodeJS).

Ihr Beispiel, in dem Sie ein Ereignis in Ihrem Code auslösen, anstatt eine Funktion direkt aufzurufen, weist einige interessantere Kompromisse auf, auf die ich weiter unten eingehen werde. Der Hauptunterschied besteht darin, dass der Herausgeber eines Ereignisses ( makeItSnow) den Empfänger des Anrufs nicht angibt. das ist anderswo verkabelt (im aufruf zu bindin deinem beispiel). Dies wird Feuer-und-Vergessen genannt : makeItSnowKündigt der Welt an, dass es schneit, aber es ist egal, wer zuhört, was als nächstes passiert oder wann es passiert - es sendet einfach die Nachricht und entstaubt seine Hände.


Der ereignisgesteuerte Ansatz entkoppelt also den Absender der Nachricht vom Empfänger. Ein Vorteil, den Sie dadurch erhalten, besteht darin, dass ein bestimmtes Ereignis mehrere Handler haben kann. Sie können eine gritRoadsFunktion an Ihr Schneeereignis binden, ohne den vorhandenen shovelSnowHandler zu beeinträchtigen . Sie haben Flexibilität bei der Verkabelung Ihrer Anwendung. Um ein Verhalten auszuschalten, müssen Sie lediglich den bindAnruf entfernen , anstatt den Code zu durchsuchen, um alle Instanzen des Verhaltens zu finden.

Ein weiterer Vorteil der ereignisgesteuerten Programmierung besteht darin, dass Sie sich übergreifend Gedanken machen können. Der Event-Dispatcher spielt die Rolle des Vermittlers , und einige Bibliotheken (z. B. Brighter ) verwenden eine Pipeline, damit Sie allgemeine Anforderungen wie Protokollierung oder Dienstgüte problemlos einbinden können.

Vollständige Offenlegung: Brighter wird bei Huddle entwickelt, wo ich arbeite.

Ein dritte Vorteil , dass der Absender eines Ereignis vom Empfänger entkoppelt ist , dass es gibt Ihnen Flexibilität bei , wenn Sie das Ereignis zu behandeln. Sie können jeden Ereignistyp in einem eigenen Thread verarbeiten (sofern Ihr Event-Dispatcher dies unterstützt), oder Sie können ausgelöste Ereignisse auf einen Nachrichtenbroker wie RabbitMQ übertragen und mit einem asynchronen Prozess verarbeiten oder sie sogar über Nacht in großen Mengen verarbeiten. Der Empfänger des Ereignisses kann sich in einem separaten Prozess oder auf einem separaten Computer befinden. Sie müssen den Code, der das Ereignis auslöst, nicht ändern, um dies zu tun! Dies ist die große Idee hinter "Microservice" -Architekturen: Autonome Dienste kommunizieren über Ereignisse, wobei Messaging-Middleware das Rückgrat der Anwendung ist.

Ein etwas anderes Beispiel für einen ereignisgesteuerten Stil finden Sie im domänengesteuerten Entwurf, bei dem Domänenereignisse verwendet werden, um die Aggregate voneinander zu trennen. Betrachten Sie beispielsweise einen Online-Shop, der Produkte basierend auf Ihrer Kaufhistorie empfiehlt. A Customermuss seine Kaufhistorie aktualisieren, wenn a ShoppingCartbezahlt wird. Das ShoppingCartAggregat kann das benachrichtigen, Customerindem es ein CheckoutCompletedEreignis auslöst. Das Customerwürde in einer separaten Transaktion als Reaktion auf das Ereignis aktualisiert werden.


Der Hauptnachteil dieses ereignisgesteuerten Modells ist die Indirektion. Es ist jetzt schwieriger, den Code zu finden, der das Ereignis behandelt, da Sie nicht einfach mit Ihrer IDE dorthin navigieren können. Sie müssen herausfinden, wo das Ereignis in der Konfiguration gebunden ist, und hoffen, dass Sie alle Handler gefunden haben. Es gibt zu jeder Zeit mehr Dinge im Kopf zu behalten. Hier können Codestilkonventionen hilfreich sein (z. B. alle Aufrufe bindin einer Datei zusammenfassen). Aus Gründen Ihrer Gesundheit ist es wichtig, nur einen Event-Dispatcher zu verwenden und ihn konsistent zu verwenden.

Ein weiterer Nachteil ist, dass es schwierig ist, Ereignisse umzugestalten. Wenn Sie das Format eines Ereignisses ändern müssen, müssen Sie auch alle Empfänger ändern. Dies wird noch verschlimmert, wenn sich die Abonnenten eines Ereignisses auf verschiedenen Rechnern befinden, da Sie jetzt Software-Releases synchronisieren müssen!

Unter bestimmten Umständen kann die Leistung von Belang sein. Bei der Bearbeitung einer Nachricht muss der Dispatcher:

  1. Suchen Sie in einer Datenstruktur nach den richtigen Handlern.
  2. Erstellen Sie für jeden Handler eine Nachrichtenverarbeitungspipeline. Dies kann eine Reihe von Speicherzuweisungen beinhalten.
  3. Rufen Sie die Handler dynamisch auf (möglicherweise mithilfe von Reflection, wenn die Sprache dies erfordert).

Dies ist sicherlich langsamer als ein regulärer Funktionsaufruf, bei dem nur ein neuer Frame auf den Stack verschoben wird. Die Flexibilität, die eine ereignisgesteuerte Architektur bietet, erleichtert jedoch das Isolieren und Optimieren von langsamem Code erheblich. Die Möglichkeit, Arbeit an einen asynchronen Prozessor zu senden, ist hier ein großer Vorteil, da Sie eine Anfrage sofort bearbeiten können, während die harte Arbeit im Hintergrund erledigt wird. Wenn Sie mit der Datenbank interagieren oder Inhalte auf dem Bildschirm zeichnen, werden die Kosten für die E / A-Verarbeitung in jedem Fall die Kosten für die Verarbeitung einer Nachricht völlig übersteigen. Es geht darum, eine vorzeitige Optimierung zu vermeiden.


Zusammenfassend lässt sich sagen, dass Events eine großartige Möglichkeit sind, lose gekoppelte Software zu erstellen, aber sie sind nicht kostenlos. Es wäre beispielsweise ein Fehler, jeden Funktionsaufruf in Ihrer Anwendung durch ein Ereignis zu ersetzen . Verwenden Sie Ereignisse, um sinnvolle architektonische Unterteilungen vorzunehmen.


2
Diese Antwort sagt dasselbe wie die Antwort von 5377, die ich als richtig ausgewählt habe; Ich ändere meine Auswahl, um diese zu markieren, da sie weiter ausgearbeitet wird.
Viziionary

1
Ist Geschwindigkeit ein wesentlicher Nachteil von ereignisgesteuertem Code? Es scheint, als könnte es sein, aber ich weiß es nicht genau.
Raptortech97

1
@raptortech97 das kann es sicher sein. Für Code, der besonders schnell sein muss, möchten Sie wahrscheinlich das Senden von Ereignissen in einer inneren Schleife vermeiden. Glücklicherweise ist in solchen Situationen in der Regel genau definiert, was Sie tun müssen, sodass Sie nicht die Flexibilität von Ereignissen (oder Publish / Subscribe oder Beobachtern, die äquivalente Mechanismen mit unterschiedlicher Terminologie sind) benötigen.
Jules

1
Beachten Sie auch, dass es einige Sprachen (z. B. Erlang) gibt, die um das Darstellermodell herum aufgebaut sind und in denen alles Nachrichten (Ereignisse) sind. In diesem Fall kann der Compiler entscheiden, ob die Meldungen / Ereignisse als direkte Funktionsaufrufe oder als Kommunikation implementiert werden sollen.
Brendan

1
Für "Leistung" müssen wir meiner Meinung nach zwischen Single-Thread-Leistung und Skalierbarkeit unterscheiden. Nachrichten / Ereignisse können für die Single-Thread-Leistung schlechter sein (sie können jedoch für null zusätzliche Kosten in Funktionsaufrufe umgewandelt und nicht schlechter sein), und für die Skalierbarkeit sind sie in nahezu jeder Hinsicht überlegen (z. B. führen sie wahrscheinlich zu massiven Leistungsverbesserungen bei modernen Multi -CPU und zukünftige "Many-CPU" -Systeme).
Brendan

25

Die ereignisbasierte Programmierung wird verwendet, wenn das Programm die von ihm ausgeführte Ereignisfolge nicht steuert. Stattdessen wird der Programmfluss von einem externen Prozess wie einem Benutzer (z. B. GUI), einem anderen System (z. B. Client / Server) oder einem anderen Prozess (z. B. RPC) gesteuert.

Zum Beispiel weiß ein Stapelverarbeitungsskript, was es tun muss, damit es es tut. Es ist nicht ereignisbasiert.

Dort sitzt ein Textverarbeitungsprogramm und wartet, bis der Benutzer mit der Eingabe beginnt. Tastendrucke sind Ereignisse, die eine Funktion zum Aktualisieren des internen Dokumentpuffers auslösen. Das Programm kann nicht wissen, was Sie eingeben möchten, daher muss es ereignisgesteuert sein.

Die meisten GUI-Programme sind ereignisgesteuert, da sie auf Benutzerinteraktion basieren. Ereignisbasierte Programme sind jedoch nicht auf GUIs beschränkt, sondern für die meisten Menschen das bekannteste Beispiel. Webserver warten darauf, dass Clients eine Verbindung herstellen, und folgen einer ähnlichen Redewendung. Hintergrundprozesse auf Ihrem Computer reagieren möglicherweise auch auf Ereignisse. Beispielsweise kann ein On-Demand-Virenscanner vom Betriebssystem ein Ereignis bezüglich einer neu erstellten oder aktualisierten Datei empfangen und diese Datei dann auf Viren prüfen.


18

In einer ereignisbasierten Anwendung können Sie mit dem Konzept der Ereignis-Listener noch mehr lose gekoppelte Anwendungen schreiben .

Beispielsweise kann ein Modul oder Plug-In eines Drittanbieters einen Datensatz aus der Datenbank löschen, dann das receordDeletedEreignis auslösen und den Rest den Ereignis-Listenern überlassen, um ihre Arbeit zu erledigen. Alles wird gut funktionieren, auch wenn das Auslösemodul nicht einmal weiß, wer auf dieses bestimmte Ereignis wartet oder was als nächstes passieren soll.


6

Eine einfache Analogie, die ich hinzufügen wollte, hat mir geholfen:

Stellen Sie sich die Komponenten (oder Objekte) Ihrer Anwendung als eine große Gruppe von Facebook-Freunden vor.

Wenn einer Ihrer Freunde Ihnen etwas mitteilen möchte, kann er Sie entweder direkt anrufen oder auf seiner Facebook-Pinnwand posten. Wenn sie es auf Facebook posten, kann jeder es sehen und darauf reagieren, aber viele Leute tun es nicht. Manchmal ist es wichtig, dass die Leute darauf reagieren müssen, wie "Wir bekommen ein Baby!" oder "So-and-so-Band gibt ein Überraschungskonzert in der Drunkin 'Clam-Bar!" Im letzten Fall werden die restlichen Freunde wahrscheinlich darauf reagieren müssen, besonders wenn sie an dieser Band interessiert sind.

Wenn Ihr Freund ein Geheimnis zwischen Ihnen und ihnen haben möchte, würde er es wahrscheinlich nicht auf seiner Facebook-Pinnwand veröffentlichen, sondern Sie direkt anrufen und Ihnen mitteilen. Stellen Sie sich ein Szenario vor, in dem Sie einem Mädchen sagen, dass Sie es gerne zu einem Date in einem Restaurant treffen würden. Anstatt sie direkt anzurufen und zu fragen, posten Sie sie auf Ihrer Facebook-Pinnwand, damit alle Ihre Freunde sie sehen können. Das funktioniert, aber wenn Sie einen eifersüchtigen Ex haben, könnte sie das sehen und im Restaurant erscheinen, um Ihren Tag zu ruinieren.

Denken Sie an diese Analogie, wenn Sie entscheiden, ob Ereignis-Listener für die Implementierung von etwas eingebaut werden sollen oder nicht. Muss diese Komponente ihr Geschäft für jeden sichtbar machen? Oder müssen sie jemanden direkt anrufen? Die Dinge können ziemlich leicht chaotisch werden, also sei vorsichtig.


0

Diese folgende Analogie kann Ihnen helfen, die ereignisgesteuerte E / A-Programmierung zu verstehen, indem Sie eine Parallele zur Warteschlange an der Arztrezeption zeichnen.

Das Blockieren von E / A ist wie wenn Sie in der Warteschlange stehen, bittet die Rezeption einen Mann vor Ihnen, das Formular auszufüllen und sie wartet, bis er fertig ist. Sie müssen warten, bis der Typ an der Reihe ist. Dies blockiert.

Wenn ein einzelner Mann 3 Minuten zum Ausfüllen benötigt, muss der zehnte bis 30 Minuten warten. Jetzt, um diese Wartezeit von 10 zu verkürzen, wäre die Lösung, die Anzahl der Empfangsmitarbeiter zu erhöhen, was teuer ist. Dies geschieht auf herkömmlichen Webservern. Wenn Sie eine Benutzerinformation anfordern, sollte die nachfolgende Anforderung durch andere Benutzer warten, bis der aktuelle Vorgang, der aus der Datenbank abgerufen wird, abgeschlossen ist. Dies erhöht die "Zeit bis zur Antwort" der 10. Anforderung und es erhöht sich exponentiell für den n-ten Benutzer. Um dies zu vermeiden, erstellt ein herkömmlicher Webserver für jede einzelne Anforderung einen Thread (was einer zunehmenden Anzahl von Empfangsmitarbeitern entspricht), dh im Grunde genommen erstellt er für jede Anforderung eine Kopie des Servers, was eine kostspielige Zeit für den CPU-Verbrauch darstellt, da für jede Anforderung ein Betriebssystem erforderlich ist Faden. So skalieren Sie die App:

Ereignisgesteuert : Der andere Ansatz, um die "Antwortzeit" der Warteschlange zu erhöhen, ist der ereignisgesteuerte Ansatz, bei dem der Typ in der Warteschlange das Formular übergibt, zum Ausfüllen aufgefordert und nach dem Ausfüllen zurückkommt. Daher kann die Rezeptionistin jederzeit Anfragen entgegennehmen. Dies ist genau das, was Javascript seit seiner Einführung getan hat. Im Browser reagiert Javascript auf Benutzer-Klickereignisse, Scrollen, Streichen oder Abrufen von Datenbanken und so weiter. Dies ist in Javascript von Natur aus möglich, da Javascript Funktionen als erstklassige Objekte behandelt und diese als Parameter an andere Funktionen (sogenannte Callbacks) übergeben und nach Abschluss einer bestimmten Aufgabe aufgerufen werden können. Genau das macht node.js auf dem Server. Weitere Informationen zum ereignisgesteuerten Programmieren und Blockieren von E / A finden Sie im Kontext des Knotens hier

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.