Im Rahmen dieses Beitrags von Igor Minar, Leiter von AngularJS:
MVC gegen MVVM gegen MVP . Was für ein kontroverses Thema, über das viele Entwickler stundenlang diskutieren und streiten können.
AngularJS war mehrere Jahre näher an MVC (oder besser gesagt an einer seiner clientseitigen Varianten), aber im Laufe der Zeit und dank vieler Refactorings und API-Verbesserungen ist es jetzt näher an MVVM - das $ scope- Objekt könnte als das ViewModel betrachtet werden , das es gibt dekoriert durch eine Funktion, die wir einen Controller nennen .
Die Möglichkeit, ein Framework zu kategorisieren und in einen der MV * -Buckets zu legen, hat einige Vorteile. Es kann Entwicklern helfen, sich mit seiner API besser vertraut zu machen, indem es die Erstellung eines mentalen Modells erleichtert, das die Anwendung darstellt, die mit dem Framework erstellt wird. Es kann auch hilfreich sein, eine Terminologie festzulegen, die von Entwicklern verwendet wird.
Trotzdem würde ich lieber sehen, wie Entwickler umwerfende Apps entwickeln, die gut gestaltet sind und der Trennung von Bedenken folgen, als dass sie Zeit damit verschwenden, über MV * -Nonsens zu streiten. Aus diesem Grund erkläre ich AngularJS hiermit zum MVW-Framework - Model-View-Whatever . Wo was auch immer für " was auch immer für Sie funktioniert " steht.
Angular bietet Ihnen viel Flexibilität, um die Präsentationslogik von der Geschäftslogik und dem Präsentationsstatus zu trennen. Verwenden Sie es, um Ihre Produktivität und Wartbarkeit der Anwendung zu steigern, anstatt heftige Diskussionen über Dinge zu führen, die am Ende des Tages nicht so wichtig sind.
Gibt es Empfehlungen oder Richtlinien für die Implementierung des AngularJS MVW-Entwurfsmusters (Model-View-Whatever) in clientseitigen Anwendungen?
quelle
Antworten:
Dank einer Vielzahl wertvoller Quellen habe ich einige allgemeine Empfehlungen für die Implementierung von Komponenten in AngularJS-Apps erhalten:
Regler
Der Controller sollte nur eine Zwischenschicht zwischen Modell und Ansicht sein. Versuche es so dünn wie möglich zu machen .
Es wird dringend empfohlen, Geschäftslogik in der Steuerung zu vermeiden . Es sollte zum Modell verschoben werden.
Der Controller kann mit anderen Controllern über den Methodenaufruf (möglich, wenn Kinder mit dem Elternteil kommunizieren möchten) oder die Methoden $ emit , $ Broadcast und $ on kommunizieren . Die gesendeten und gesendeten Nachrichten sollten auf ein Minimum beschränkt werden.
Der Controller sollte sich nicht um Präsentation oder DOM-Manipulation kümmern .
Versuchen Sie , verschachtelte Controller zu vermeiden . In diesem Fall wird der übergeordnete Controller als Modell interpretiert. Injizieren Sie stattdessen Modelle als Shared Services.
Scope in der Steuerung sollte verwendet werden Bindungsmodell mit Blick und
Einkapseln Ansicht Modell für Presentation Model Design - Muster.
Umfang
Behandeln Sie den Bereich in Vorlagen als schreibgeschützt und in Controllern als schreibgeschützt . Der Zweck des Bereichs besteht darin, sich auf das Modell zu beziehen, nicht auf das Modell.
Stellen Sie beim bidirektionalen Binden (ng-Modell) sicher, dass Sie nicht direkt an die Bereichseigenschaften binden.
Modell
Das Modell in AngularJS ist ein vom Dienst definierter Singleton .
Das Modell bietet eine hervorragende Möglichkeit, Daten und Anzeige zu trennen.
Modelle sind Hauptkandidaten für Unit-Tests, da sie normalerweise genau eine Abhängigkeit haben (irgendeine Form von Ereignisemitter, im allgemeinen Fall $ rootScope ) und eine hoch testbare Domänenlogik enthalten .
Das Modell sollte als Implementierung einer bestimmten Einheit betrachtet werden. Es basiert auf dem Prinzip der Einzelverantwortung. Unit ist eine Instanz, die für ihren eigenen Umfang verwandter Logik verantwortlich ist, die eine einzelne Entität in der realen Welt darstellen und in der Programmierwelt in Bezug auf Daten und Status beschreiben kann .
Das Modell sollte die Daten Ihrer Anwendung kapseln und eine API für den Zugriff auf und die Bearbeitung dieser Daten bereitstellen .
Das Modell sollte tragbar sein, damit es problemlos zu einer ähnlichen Anwendung transportiert werden kann.
Durch das Isolieren der Einheitenlogik in Ihrem Modell haben Sie das Auffinden, Aktualisieren und Warten vereinfacht.
Das Modell kann Methoden allgemeinerer globaler Modelle verwenden, die für die gesamte Anwendung gelten.
Versuchen Sie, die Zusammensetzung anderer Modelle in Ihrem Modell mithilfe der Abhängigkeitsinjektion zu vermeiden, wenn es nicht wirklich darauf ankommt, die Kopplung der Komponenten zu verringern und die Testbarkeit und Verwendbarkeit der Einheiten zu verbessern .
Vermeiden Sie die Verwendung von Ereignis-Listenern in Modellen. Dies erschwert das Testen und tötet Modelle im Allgemeinen nach dem Prinzip der Einzelverantwortung.
Modellimplementierung
Da das Modell eine gewisse Logik in Bezug auf Daten und Status enthalten sollte, sollte es den Zugriff auf seine Mitglieder architektonisch einschränken, damit wir eine lose Kopplung gewährleisten können.
In der AngularJS-Anwendung können Sie dies mithilfe des werkseitigen Servicetyps definieren . Auf diese Weise können wir private Eigenschaften und Methoden sehr einfach definieren und auch öffentlich zugängliche Eigenschaften an einem einzigen Ort zurückgeben, sodass sie für Entwickler wirklich lesbar sind.
Ein Beispiel :
Neue Instanzen erstellen
Vermeiden Sie es, über eine Factory zu verfügen, die eine neue Funktion zurückgibt, da dies die Abhängigkeitsinjektion beeinträchtigt und sich die Bibliothek insbesondere für Dritte unangenehm verhält.
Eine bessere Möglichkeit, dasselbe zu erreichen, besteht darin, die Factory als API zu verwenden, um eine Sammlung von Objekten mit den damit verbundenen Getter- und Setter-Methoden zurückzugeben.
Globales Modell
Versuchen Sie im Allgemeinen, solche Situationen zu vermeiden und Ihre Modelle richtig zu gestalten, damit sie in die Steuerung eingespritzt und aus Ihrer Sicht verwendet werden können.
In einigen Fällen erfordern einige Methoden eine globale Zugänglichkeit innerhalb der Anwendung. Um dies zu ermöglichen, können Sie die Eigenschaft ' common ' in $ rootScope definieren und sie während des Bootstraps der Anwendung an commonModel binden :
Alle Ihre globalen Methoden befinden sich in einem " gemeinsamen " Eigentum. Dies ist eine Art Namespace .
Definieren Sie jedoch keine Methoden direkt in Ihrem $ rootScope . Dies kann zu unerwartetem Verhalten führen, wenn es mit der ngModel-Direktive in Ihrem Ansichtsbereich verwendet wird, was im Allgemeinen Ihren Bereich verschmutzt und dazu führt, dass Bereichsmethoden Probleme überschreiben.
Ressource
Mit Resource können Sie mit verschiedenen Datenquellen interagieren .
Sollte nach dem Prinzip der Einzelverantwortung umgesetzt werden .
Im besonderen Fall ist es wiederverwendbar Proxy für HTTP / JSON-Endpunkte.
Ressourcen werden in Modelle eingefügt und bieten die Möglichkeit, Daten zu senden / abzurufen.
Ressourcenimplementierung
Eine Factory, die ein Ressourcenobjekt erstellt, mit dem Sie mit serverseitigen RESTful-Datenquellen interagieren können.
Das zurückgegebene Ressourcenobjekt verfügt über Aktionsmethoden, die Verhalten auf hoher Ebene bereitstellen, ohne dass mit dem $ http-Dienst auf niedriger Ebene interagiert werden muss.
Dienstleistungen
Sowohl Modell als auch Ressource sind Dienste .
Dienste sind nicht zugeordnet, lose gekoppelt Funktionseinheiten, die in sich geschlossen sind.
Dienste sind eine Funktion, die Angular von der Serverseite aus für clientseitige Webanwendungen bereitstellt, wo Dienste seit langem häufig verwendet werden.
Dienste in Angular-Apps sind austauschbare Objekte, die mithilfe der Abhängigkeitsinjektion miteinander verbunden werden.
Angular bietet verschiedene Arten von Diensten. Jeder mit seinen eigenen Anwendungsfällen. Weitere Informationen finden Sie unter Grundlegendes zu Servicetypen.
Versuchen Sie, die Hauptprinzipien der Servicearchitektur in Ihrer Anwendung zu berücksichtigen .
Im Allgemeinen laut Web Services Glossar :
Client-seitige Struktur
Im Allgemeinen wird die Client-Seite der Anwendung in Module aufgeteilt . Jedes Modul sollte als Einheit testbar sein.
Versuchen Sie, Module abhängig von Merkmal / Funktionalität oder Ansicht zu definieren , nicht nach Typ. Weitere Informationen finden Sie in der Präsentation von Misko .
Modulkomponenten können herkömmlicherweise nach Typen wie Steuerungen, Modellen, Ansichten, Filtern, Anweisungen usw. gruppiert werden.
Das Modul selbst bleibt jedoch wiederverwendbar , übertragbar und testbar .
Für Entwickler ist es auch viel einfacher, einige Teile des Codes und alle seine Abhängigkeiten zu finden.
Bitte beachten Sie - Code - Organisation in Groß AngularJS und JavaScript - Anwendungen für weitere Einzelheiten.
Ein Beispiel für die Strukturierung von Ordnern :
Ein gutes Beispiel für die Strukturierung von Winkelanwendungen ist die Angular-App - https://github.com/angular-app/angular-app/tree/master/client/src
Dies wird auch von modernen Anwendungsgeneratoren berücksichtigt - https://github.com/yeoman/generator-angular/issues/109
quelle
searchModel
folgt das Beispiel von nicht den Empfehlungen zur Wiederverwendbarkeit. Es wäre besser, die Konstanten über denconstant
Dienst zu importieren . 3. Irgendeine Erklärung, was hier gemeint ist?:Try to avoid having a factory that returns a new able function
prototype
bricht die Vererbung, stattdessen kann man verwendenCar.prototype.save = ...
object
in Ihrem bidirektionalen Bindungsausdruck zu verwenden, um sicherzustellen, dass Sie in die genaue Eigenschaft odersetter
Funktion schreiben . Wenn Sie die direkte Eigenschaft Ihres Bereichs ( ohne Punkt ) verwenden, besteht das Risiko, dass Sie die gewünschte Zieleigenschaft mit der neu erstellten im nächstgelegenen oberen Bereich der Prototypenkette ausblenden, wenn Sie darauf schreiben. Dies wird besser in Miskos Präsentation erklärtIch glaube, dass Igor dies, wie aus dem von Ihnen bereitgestellten Zitat hervorgeht, nur die Eisbergspitze eines weitaus größeren Problems ist.
MVC und seine Derivate (MVP, PM, MVVM) sind alle gut und gut innerhalb eines einzelnen Agenten, aber eine Server-Client-Architektur ist für alle Zwecke ein System mit zwei Agenten, und die Leute sind oft so besessen von diesen Mustern, dass sie das vergessen Das vorliegende Problem ist weitaus komplexer. Wenn sie versuchen, diese Prinzipien einzuhalten, erhalten sie tatsächlich eine fehlerhafte Architektur.
Lass uns das Stück für Stück machen.
Die Richtlinien
Ansichten
Im Winkelkontext ist die Ansicht das DOM. Die Richtlinien sind:
Machen:
Nicht:
So verlockend, kurz und harmlos das aussieht:
Es bedeutet für jeden Entwickler, dass er nun sowohl die Javascript-Dateien als auch die HTML-Dateien überprüfen muss, um zu verstehen, wie das System funktioniert.
Controller
Machen:
Nicht:
Der Grund für die letzte Richtlinie ist, dass Controller Controller von Ansichten sind, keine Entitäten. noch sind sie wiederverwendbar.
Man könnte argumentieren, dass Direktiven wiederverwendbar sind, aber auch Direktiven sind Schwestern der Ansichten (DOM) - sie sollten niemals Entitäten entsprechen.
Sicher, manchmal stellen Ansichten Entitäten dar, aber das ist ein ziemlich spezifischer Fall.
Mit anderen Worten, Controller sollten sich auf die Präsentation konzentrieren. Wenn Sie die Geschäftslogik einbringen, werden Sie wahrscheinlich nicht nur einen aufgeblasenen, wenig verwaltbaren Controller haben, sondern Sie verletzen auch das Prinzip der Trennung von Bedenken .
Daher sind Controller in Angular eher Präsentationsmodelle oder MVVM .
Und wenn Controller sich nicht mit Geschäftslogik befassen sollten, wer sollte das tun?
Was ist ein Modell?
Ihr Kundenmodell ist oft teilweise und veraltet
Sofern Sie keine Offline-Webanwendung oder eine schrecklich einfache Anwendung (wenige Entitäten) schreiben, ist es sehr wahrscheinlich, dass Ihr Client-Modell:
Das reale Modell muss bestehen bleiben
Im traditionellen MCV ist das Modell das einzige, was beibehalten wird . Wann immer wir über Modelle sprechen, müssen diese irgendwann beibehalten werden. Ihr Client kann Modelle nach Belieben bearbeiten, aber bis der Roundtrip zum Server erfolgreich abgeschlossen wurde, ist die Aufgabe noch nicht erledigt.
Folgen
Die beiden oben genannten Punkte sollten als Vorsicht dienen - das Modell, das Ihr Kunde besitzt, kann nur eine teilweise, meist einfache Geschäftslogik beinhalten.
Daher ist es im Client-Kontext möglicherweise ratsam , Kleinbuchstaben zu verwenden
M
- es handelt sich also wirklich um mVC , mVP und mVVm . Das großeM
ist für den Server.Geschäftslogik
Vielleicht ist eines der wichtigsten Konzepte für Geschäftsmodelle, dass Sie sie in zwei Typen unterteilen können (ich lasse das dritte View-Business weg, da dies eine Geschichte für einen anderen Tag ist):
firstName
undsirName
Eigenschaften an, ein Getter wiegetFullName()
kann als anwendungsunabhängig betrachtet werden.Es ist wichtig zu betonen, dass beide innerhalb eines Kundenkontexts keine „echte“ Geschäftslogik sind - sie befassen sich nur mit dem Teil davon, der für den Kunden wichtig ist. Die Anwendungslogik (nicht die Domänenlogik) sollte dafür verantwortlich sein, die Kommunikation mit dem Server und die meisten Benutzerinteraktionen zu erleichtern. Die Domänenlogik ist weitgehend klein, entitätsspezifisch und präsentationsgesteuert.
Die Frage bleibt weiterhin: Wo werfen Sie sie in eine eckige Anwendung?
3 vs 4-Schicht-Architektur
Alle diese MVW-Frameworks verwenden drei Ebenen:
Bei Kunden gibt es jedoch zwei grundlegende Probleme:
Eine Alternative zu dieser Strategie ist die 4-Schicht-Strategie :
Das eigentliche Problem hierbei ist die Schicht mit den Geschäftsregeln für Anwendungen (Anwendungsfälle), die bei Kunden häufig nicht funktioniert.
Diese Schicht wird von Interaktoren (Onkel Bob) realisiert, was Martin Fowler so ziemlich eine Operationsschicht-Service-Schicht nennt .
Konkretes Beispiel
Betrachten Sie die folgende Webanwendung:
Ein paar Dinge sollten jetzt passieren:
Wo werfen wir das alles hin?
Wenn Ihre Architektur einen Controller umfasst, der aufruft
$resource
, geschieht dies alles innerhalb des Controllers. Aber es gibt eine bessere Strategie.Eine vorgeschlagene Lösung
Das folgende Diagramm zeigt, wie das obige Problem durch Hinzufügen einer weiteren Anwendungslogikschicht in Angular-Clients gelöst werden kann:
Also fügen wir $ resource eine Ebene zwischen Controller hinzu, diese Ebene (nennen wir es Interaktor ):
UserInteractor
.Und so mit den Anforderungen des obigen konkreten Beispiels:
validate()
validate()
.createUser()
quelle
Ein kleines Problem im Vergleich zu den großen Ratschlägen in Artems Antwort, aber in Bezug auf die Lesbarkeit des Codes fand ich es am besten, die API vollständig im
return
Objekt zu definieren , um das Hin- und Hergehen im Code zu minimieren und zu prüfen, ob Variablen definiert sind:Wenn das
return
Objekt "zu überfüllt" aussieht, ist dies ein Zeichen dafür, dass der Dienst zu viel tut.quelle
AngularJS implementiert MVC nicht auf herkömmliche Weise, sondern implementiert etwas, das näher an MVVM (Model-View-ViewModel) liegt. ViewModel kann auch als Binder bezeichnet werden (im Winkelfall kann es $ scope sein). Das Modell -> Wie wir wissen, kann das Modell im Winkel einfach nur alte JS-Objekte oder die Daten in unserer Anwendung sein
Die Ansicht -> die Ansicht in angleJS ist der HTML-Code, der von angleJS durch Anwenden der Anweisungen oder Anweisungen oder Bindungen analysiert und kompiliert wurde. Der Hauptpunkt hier ist in angle, dass die Eingabe nicht nur die einfache HTML-Zeichenfolge (innerHTML) ist, sondern es ist ein vom Browser erstelltes DOM.
Das ViewModel -> ViewModel ist tatsächlich der Binder / die Brücke zwischen Ihrer Ansicht und dem Modell, wenn es sich um $ scope handelt, um den von uns verwendeten $ scope zu initialisieren und zu erweitern.
Wenn ich die Antwort zusammenfassen möchte: In der angleJS-Anwendung verweist $ scope auf die Daten, Controller steuert das Verhalten und View verwaltet das Layout, indem es mit dem Controller interagiert, um sich entsprechend zu verhalten.
quelle
Um die Frage klar zu formulieren, verwendet Angular verschiedene Entwurfsmuster, die wir bereits in unserer regulären Programmierung angetroffen haben. 1) Wenn wir unsere Steuerungen oder Anweisungen, Fabriken, Dienste usw. in Bezug auf unser Modul registrieren. Hier versteckt es die Daten vor dem globalen Raum. Welches ist Modulmuster . 2) Wenn Angular seine Dirty-Prüfung zum Vergleichen der Bereichsvariablen verwendet, wird hier das Beobachtermuster verwendet . 3) Alle übergeordneten untergeordneten Bereiche in unseren Controllern verwenden das prototypische Muster. 4) Beim Injizieren der Dienste wird das Factory-Muster verwendet .
Insgesamt werden verschiedene bekannte Entwurfsmuster verwendet, um die Probleme zu lösen.
quelle