Wir haben eine Web-App, in der wir viele (> 50) kleine WebComponents haben , die miteinander interagieren.
Um alles entkoppelt zu halten, haben wir in der Regel, dass keine Komponente direkt auf eine andere verweisen kann. Stattdessen lösen Komponenten Ereignisse aus, die dann (in der "Haupt" -App) verdrahtet werden, um die Methoden einer anderen Komponente aufzurufen.
Mit der Zeit wurden immer mehr Komponenten hinzugefügt und die "Haupt" -App-Datei wurde mit Codeblöcken übersät, die so aussahen:
buttonsToolbar.addEventListener('request-toggle-contact-form-modal', () => {
contactForm.toggle()
})
buttonsToolbar.addEventListener('request-toggle-bug-reporter-modal', () => {
bugReporter.toggle()
})
// ... etc
Um dies zu verbessern, haben wir ähnliche Funktionen zusammengefasst, in a Class
, nennen Sie es etwas Relevantes, übergeben Sie die beteiligten Elemente beim Instanziieren und behandeln Sie die "Verkabelung" innerhalb derClass
, wie folgt:
class Contact {
constructor(contactForm, bugReporter, buttonsToolbar) {
this.contactForm = contactForm
this.bugReporterForm = bugReporterForm
this.buttonsToolbar = buttonsToolbar
this.buttonsToolbar
.addEventListener('request-toggle-contact-form-modal', () => {
this.toggleContactForm()
})
this.buttonsToolbar
.addEventListener('request-toggle-bug-reporter-modal', () => {
this.toggleBugReporterForm()
})
}
toggleContactForm() {
this.contactForm.toggle()
}
toggleBugReporterForm() {
this.bugReporterForm.toggle()
}
}
und wir instanziieren so:
<html>
<contact-form></contact-form>
<bug-reporter></bug-reporter>
<script>
const contact = new Contact(
document.querySelector('contact-form'),
document.querySelector('bug-form')
)
</script>
</html>
Ich bin es wirklich leid, eigene Muster einzuführen, insbesondere solche, die seit meiner Verwendung nicht wirklich OOP-y sind Classes
als Initialisierungscontainer verwende, kein besseres Wort habe.
Gibt es ein besseres / bekannteres definiertes Muster für die Bearbeitung dieser Art von Aufgaben, die mir fehlen?
quelle
Antworten:
Der Code, den Sie haben, ist ziemlich gut. Etwas abstoßend erscheint, dass der Initialisierungscode nicht Teil des Objekts selbst ist. Das heißt, Sie können ein Objekt instanziieren, aber wenn Sie vergessen, seine Verdrahtungsklasse aufzurufen, ist es nutzlos.
Stellen Sie sich ein Notification Center (auch bekannt als Event Bus) vor, das ungefähr so definiert ist:
Dies ist ein DIY-Multi-Dispatch-Event-Handler. Sie können dann Ihre eigene Verkabelung vornehmen, indem Sie einfach ein NotificationCenter als Konstruktorargument benötigen. Das Senden von Nachrichten und das Warten darauf, dass die Nutzdaten weitergeleitet werden, ist der einzige Kontakt, den Sie mit dem System haben. Daher ist es sehr FEST.
Hinweis: Ich habe In-Place-String-Literale verwendet, damit die Schlüssel dem in der Frage verwendeten Stil entsprechen und der Einfachheit halber. Dies ist aufgrund des Risikos von Tippfehlern nicht ratsam. Verwenden Sie stattdessen eine Aufzählung oder Zeichenfolgenkonstanten.
Im obigen Code ist die Symbolleiste dafür verantwortlich, das NotificationCenter über die Art der Ereignisse zu informieren, an denen es interessiert ist, und alle externen Interaktionen über die Benachrichtigungsmethode zu veröffentlichen. Jede andere Klasse, die sich für das interessiert,
toolbar-button-click-event
würde sich einfach in ihrem Konstruktor dafür registrieren.Interessante Variationen dieses Musters sind:
Interessante Funktionen sind:
Interessante Fallstricke und mögliche Abhilfemaßnahmen sind:
quelle
buttonsToolbar
in ein anderes Projekt, das keinen Ereignisbus verwendet?Früher habe ich einen "Ereignisbus" eingeführt, und in späteren Jahren habe ich mich immer mehr auf das Dokumentobjektmodell selbst verlassen, um Ereignisse für UI-Code zu kommunizieren.
In einem Browser ist das DOM die einzige Abhängigkeit, die immer vorhanden ist - auch beim Laden der Seite. Der Schlüssel besteht darin, benutzerdefinierte Ereignisse in JavaScript zu verwenden und sich auf das Sprudeln von Ereignissen zu verlassen, um diese Ereignisse zu kommunizieren.
Bevor Benutzer vor dem Anhängen von Abonnenten über "Warten auf die Fertigstellung des Dokuments" rufen,
document.documentElement
verweist die Eigenschaft auf das<html>
Element ab dem Zeitpunkt, an dem JavaScript ausgeführt wird, unabhängig davon, wo das Skript importiert wird oder in welcher Reihenfolge es in Ihrem Markup angezeigt wird.Hier können Sie nach Ereignissen suchen.
Es ist sehr üblich, dass eine JavaScript-Komponente (oder ein Widget) in einem bestimmten HTML-Tag auf der Seite vorhanden ist. Im "root" -Element der Komponente können Sie Ihre Bubbling-Ereignisse auslösen. Abonnenten des
<html>
Elements erhalten diese Benachrichtigungen wie jedes andere vom Benutzer generierte Ereignis.Nur ein Beispiel für einen Kesselplattencode:
So wird das Muster:
document.documentElement
Grundstück (Sie müssen nicht warten, bis das Dokument fertig ist).document.documentElement
.Dies sollte sowohl für funktionale als auch für objektorientierte Codebasen funktionieren.
quelle
Ich verwende denselben Stil bei meiner Videospielentwicklung mit Unity 3D. Ich erstelle Komponenten wie Gesundheit, Eingabe, Statistik, Sound usw. und füge sie einem Spielobjekt hinzu, um das Spielobjekt aufzubauen. Unity verfügt bereits über Mechaniken zum Hinzufügen von Komponenten zu Spielobjekten. Was ich jedoch fand, war, dass fast jeder nach Komponenten fragte oder Komponenten in anderen Komponenten direkt referenzierte (selbst wenn sie Schnittstellen verwendeten, ist es immer noch gekoppelter, danke, dass ich es vorgezogen habe). Ich wollte, dass Komponenten isoliert und ohne Abhängigkeiten von anderen Komponenten erstellt werden können. Ich ließ die Komponenten Ereignisse auslösen, wenn sich Daten änderten (spezifisch für die Komponente), und Methoden deklarieren, um Daten grundsätzlich zu ändern. Dann hat das Spielobjekt, für das ich eine Klasse erstellt habe, alle Komponentenereignisse mit anderen Komponentenmethoden verknüpft.
Das, was ich daran mag, ist, dass ich mir nur diese 1 Klasse ansehen kann, um alle Interaktionen von Komponenten für ein Spielobjekt zu sehen. Es hört sich so an, als ob Ihre Kontaktklasse meinen Spielobjektklassen sehr ähnlich ist (ich benenne Spielobjekte zu dem Objekt, das sie wie MainPlayer, Orc usw. sein sollten).
Diese Klassen sind eine Art Manager-Klassen. Sie selbst haben eigentlich nichts anderes als Instanzen von Komponenten und den Code, um sie anzuschließen. Ich bin mir nicht sicher, warum Sie hier Methoden erstellen, die nur andere Komponentenmethoden aufrufen, wenn Sie sie einfach direkt anschließen könnten. In dieser Klasse geht es eigentlich nur darum, die Veranstaltung zu organisieren.
Als Randnotiz für meine Ereignisregistrierungen habe ich einen Filter-Rückruf und einen Args-Rückruf hinzugefügt. Wenn das Ereignis ausgelöst wird (ich habe meine eigene benutzerdefinierte Ereignisklasse erstellt), ruft es einen Filterrückruf auf, falls einer vorhanden ist, und wenn es true zurückgibt, wechselt es zum args-Rückruf. Der Zweck des Filterrückrufs bestand darin, Flexibilität zu bieten. Ein Ereignis kann aus verschiedenen Gründen ausgelöst werden, aber ich möchte mein angeschlossenes Ereignis nur aufrufen, wenn eine Prüfung wahr ist. Ein Beispiel könnte sein, dass eine Eingabekomponente ein OnKeyHit-Ereignis hat. Wenn ich eine Bewegungskomponente habe, die Methoden wie MoveForward () MoveBackward () usw. enthält, kann ich OnKeyHit + = MoveForward anschließen, aber natürlich möchte ich mit keinem Tastendruck vorwärts gehen. Ich würde es nur tun wollen, wenn der Schlüssel "w" ist. Da OnKeyHit Argumente ausfüllt, die weitergegeben werden sollen, und einer davon der Schlüssel ist, der getroffen wurde,
Für mich sieht das Abonnement für eine bestimmte Game Object Manager-Klasse eher so aus:
Da Komponenten isoliert entwickelt werden können, könnten mehrere Programmierer sie codiert haben. Im obigen Beispiel gab der Eingabecodierer dem Argumentobjekt eine Variable mit dem Namen Key. Der Entwickler der Movement-Komponente hat jedoch möglicherweise nicht Key verwendet (wenn er die Argumente überprüfen musste, in diesem Fall wahrscheinlich nicht, aber in anderen Fällen werden die übergebenen Argumentwerte verwendet). Um diese Kommunikationsanforderung zu entfernen, fungiert der args-Rückruf als Zuordnung für die Argumente zwischen Komponenten. Die Person, die diese Spielobjekt-Manager-Klasse erstellt, muss also nur die arg-Variablennamen zwischen den beiden Clients kennen, wenn sie sie verkabeln und an diesem Punkt die Zuordnung durchführen. Diese Methode wird nach der Filterfunktion aufgerufen.
In der obigen Situation hat die Eingabe-Person eine Variable innerhalb des args-Objekts 'Key' benannt, aber die Bewegung hat sie 'keyPressed' genannt. Dies hilft dabei, die Isolation zwischen den Komponenten selbst während der Entwicklung zu fördern, und überträgt sie auf den Implementierer der Manager-Klasse, um eine korrekte Verbindung herzustellen.
quelle
Für das, was es wert ist, mache ich etwas als Teil eines Backend-Projekts und habe einen ähnlichen Ansatz gewählt:
Wie ich mit Ihren Konstruktions- / Verkabelungsherausforderungen umgegangen bin:
Die Analogie zu Ihrer Symbolleiste:
Probleme, mit denen Sie möglicherweise konfrontiert sind:
.filter
nach Status habe (tatsächlich ist die Implementierung funktionaler - die Konversation ist eine flache Karte von Observablen aus einem Observablen von Zustandsänderungsereignissen).Zusammenfassend können Sie also Ihre gesamte Problemdomäne als Observable oder 'funktional' / 'deklarativ' betrachten und Ihre Webkomponenten als Ereignisströme betrachten, als Observables, die vom Bus (einem Observable) abgeleitet sind, zu dem der Bus (an Beobachter) ist ebenfalls abonniert. Die Instanziierung von Observablen (z. B. eine neue Symbolleiste) ist insofern deklarativ, als der gesamte Prozess als Observable von Observablen
.map
'd aus dem Eingabestream angesehen werden kann.quelle