W kontekście tego posta Igora Minara, szefa AngularJS:
MVC vs MVVM vs MVP . Co za kontrowersyjny temat, na który wielu programistów może spędzać wiele godzin debatując i kłócąc się.
Przez kilka lat AngularJS był bliżej MVC (a raczej jednego z jego wariantów po stronie klienta), ale z biegiem czasu i dzięki wielu refaktoryzacjom i ulepszeniom interfejsu API, jest teraz bliżej MVVM - obiekt $ scope można uznać za ViewModel, który jest obecnie ozdobiony funkcją, którą nazywamy kontrolerem .
Możliwość kategoryzacji frameworka i umieszczenia go w jednym z wiader MV * ma kilka zalet. Może pomóc programistom poczuć się bardziej komfortowo z interfejsem API, ułatwiając tworzenie modelu mentalnego reprezentującego aplikację, która jest tworzona za pomocą frameworka. Może również pomóc w ustaleniu terminologii używanej przez programistów.
Powiedziałbym, że wolałbym, aby programiści tworzyli odlotowe aplikacje, które są dobrze zaprojektowane i podążają za oddzielnymi problemami, niż patrzeć, jak marnują czas na kłótnie o bzdury MV *. Z tego powodu niniejszym oświadczam, że AngularJS jest frameworkiem MVW - Model-View-Cokolwiek . Gdzie Cokolwiek oznacza „ cokolwiek działa dla Ciebie ”.
Angular zapewnia dużą elastyczność, aby ładnie oddzielić logikę prezentacji od logiki biznesowej i stanu prezentacji. Użyj go, aby zwiększyć produktywność i łatwość obsługi aplikacji, a nie gorące dyskusje o rzeczach, które ostatecznie nie mają tak dużego znaczenia.
Czy są jakieś zalecenia lub wytyczne dotyczące implementacji wzorca projektowego AngularJS MVW (Model-View-Whokolwiek) w aplikacjach po stronie klienta?
źródło
Odpowiedzi:
Dzięki ogromnej ilości cennych źródeł otrzymałem kilka ogólnych zaleceń dotyczących implementacji komponentów w aplikacjach AngularJS:
Kontroler
Kontroler powinien być tylko warstwą pośrednią między modelem a widokiem. Postaraj się, aby był tak cienki, jak to tylko możliwe.
Zdecydowanie zaleca się unikanie logiki biznesowej w kontrolerze. Należy go przenieść do modelu.
Kontroler może komunikować się z innymi kontrolerami za pomocą wywołania metody (możliwe, gdy dzieci chcą komunikować się z rodzicem) lub metod $ emit , $ broadcast i $ on . Emitowane i nadawane wiadomości powinny być ograniczone do minimum.
Kontroler nie powinien przejmować się prezentacją ani manipulacją DOM.
Staraj się unikać zagnieżdżonych kontrolerów . W tym przypadku kontroler nadrzędny jest interpretowany jako model. Zamiast tego wstrzykuj modele jako usługi wspólne.
Zakres w kontrolerze powinien być używany do wiązania modelu z widokiem i
hermetyzowania modelu widoku, jak w przypadku wzorca projektowego modelu prezentacji .
Zakres
Traktuj zakres jako tylko do odczytu w szablonach i tylko do zapisu w kontrolerach . Celem zakresu jest odwoływanie się do modelu, a nie bycie modelem.
Podczas wykonywania dwukierunkowego wiązania (model ng) upewnij się, że nie tworzysz powiązania bezpośrednio z właściwościami zakresu.
Model
Model w AngularJS to singleton zdefiniowany przez usługę .
Model zapewnia doskonały sposób oddzielania danych i wyświetlania.
Modele są głównymi kandydatami do testów jednostkowych, ponieważ zazwyczaj mają dokładnie jedną zależność (pewną formę emitera zdarzeń, w typowym przypadku $ rootScope ) i zawierają wysoce testowalną logikę domeny .
Model należy traktować jako realizację konkretnej jednostki. Opiera się na zasadzie pojedynczej odpowiedzialności. Jednostka jest instancją odpowiedzialną za własny zakres powiązanej logiki, która może reprezentować pojedynczą jednostkę w świecie rzeczywistym i opisywać ją w świecie programowania pod względem danych i stanu .
Model powinien hermetyzować dane aplikacji i zapewniać interfejs API do uzyskiwania dostępu do tych danych i manipulowania nimi.
Model powinien być przenośny, aby można go było łatwo przenosić do podobnej aplikacji.
Wyodrębnienie logiki jednostki w modelu ułatwiło zlokalizowanie, aktualizację i konserwację.
Model może wykorzystywać metody bardziej ogólnych modeli globalnych, które są wspólne dla całej aplikacji.
Staraj się unikać łączenia innych modeli z modelem przy użyciu wstrzykiwania zależności, jeśli nie jest to tak naprawdę zależne od zmniejszenia sprzężenia komponentów i zwiększenia testowalności i użyteczności jednostek .
Staraj się unikać detektorów zdarzeń w modelach. Utrudnia ich testowanie i generalnie zabija modele w kategoriach zasady pojedynczej odpowiedzialności.
Implementacja modelu
Ponieważ model powinien zawierać pewną logikę w zakresie danych i stanu, powinien architektonicznie ograniczać dostęp do swoich elementów, dzięki czemu możemy zagwarantować luźne powiązania.
Sposobem na to w aplikacji AngularJS jest zdefiniowanie go za pomocą usługi fabrycznej . Pozwoli nam to bardzo łatwo zdefiniować prywatne właściwości i metody, a także zwrócić publicznie dostępne w jednym miejscu, dzięki czemu będzie naprawdę czytelny dla programisty.
Przykład :
Tworzenie nowych instancji
Staraj się unikać fabryki, która zwraca nową możliwą funkcję, ponieważ zaczyna to rozkładać wstrzykiwanie zależności, a biblioteka będzie zachowywać się niezręcznie, szczególnie dla osób trzecich.
Lepszym sposobem osiągnięcia tego samego jest użycie fabryki jako interfejsu API do zwracania kolekcji obiektów z dołączonymi do nich metodami pobierającymi i ustawiającymi.
Model globalny
Ogólnie rzecz biorąc, staraj się unikać takich sytuacji i odpowiednio projektuj modele, aby można je było wstrzyknąć do kontrolera i użyć w swoim widoku.
W szczególnym przypadku niektóre metody wymagają globalnej dostępności w aplikacji. Aby było to możliwe, możesz zdefiniować właściwość ' common ' w $ rootScope i powiązać ją z commonModel podczas ładowania aplikacji:
Wszystkie twoje globalne metody będą funkcjonować w ramach „ wspólnej ” własności. To jest jakaś przestrzeń nazw .
Ale nie definiuj żadnych metod bezpośrednio w $ rootScope . Może to prowadzić do nieoczekiwanego zachowania, gdy jest używane z dyrektywą ngModel w zakresie widoku, generalnie zaśmiecając zakres i prowadzi do nadpisania metod zakresu.
Ratunek
Zasób umożliwia interakcję z różnymi źródłami danych .
Powinien być wdrażany zgodnie z zasadą pojedynczej odpowiedzialności .
W szczególności jest to serwer proxy wielokrotnego użytku do punktów końcowych HTTP / JSON.
Zasoby są wprowadzane do modeli i dają możliwość wysyłania / pobierania danych.
Wdrażanie zasobów
Fabryka, która tworzy obiekt zasobów, który umożliwia interakcję ze źródłami danych po stronie serwera zgodnymi ze specyfikacją REST.
Zwrócony obiekt zasobu ma metody akcji, które zapewniają zachowania wysokiego poziomu bez konieczności interakcji z usługą $ http niskiego poziomu.
Usługi
Zarówno model, jak i zasób to usługi .
Usługi to niepowiązane, luźno powiązane jednostki funkcjonalności, które są niezależne.
Usługi to funkcja, którą Angular przenosi do aplikacji internetowych po stronie klienta po stronie serwera, gdzie usługi są powszechnie używane od dawna.
Usługi w aplikacjach Angular to obiekty zastępcze, które są połączone ze sobą przy użyciu iniekcji zależności.
Angular oferuje różne rodzaje usług. Każdy ma własne przypadki użycia. Aby uzyskać szczegółowe informacje, przeczytaj artykuł Zrozumienie typów usług .
Spróbuj rozważyć główne zasady architektury usług w swojej aplikacji.
Ogólnie zgodnie z Glosariuszem usług internetowych :
Struktura po stronie klienta
Ogólnie rzecz biorąc, strona klienta aplikacji jest podzielona na moduły . Każdy moduł powinien być testowalny jako jednostka.
Spróbuj zdefiniować moduły w zależności od funkcji / funkcjonalności lub widoku , a nie według typu. Zobacz prezentację Misko po szczegóły.
Komponenty modułów można konwencjonalnie pogrupować według typów, takich jak kontrolery, modele, widoki, filtry, dyrektywy itp.
Ale sam moduł pozostaje wielokrotnego użytku , przenoszalny i testowalny .
Programiści mogą również znacznie łatwiej znaleźć niektóre części kodu i wszystkie jego zależności.
Szczegółowe informacje można znaleźć w sekcji Organizacja kodu w dużych aplikacjach AngularJS i JavaScript .
Przykład struktury folderów :
Dobry przykład strukturyzacji aplikacji kątowych jest realizowany przez angular-app - https://github.com/angular-app/angular-app/tree/master/client/src
Uwzględniają to również nowoczesne generatory aplikacji - https://github.com/yeoman/generator-angular/issues/109
źródło
searchModel
nie jest zgodny z zaleceniami dotyczącymi ponownego użycia. Byłoby lepiej importować stałe za pośrednictwemconstant
usługi. 3. Jakieś wyjaśnienie, o co tu chodzi ?:Try to avoid having a factory that returns a new able function
prototype
właściwości obiektu przerywa dziedziczenie, zamiast tego można użyćCar.prototype.save = ...
object
w twoim dwukierunkowym wyrażeniu wiążącym, aby upewnić się, że piszesz do dokładnej właściwości lubsetter
funkcji. W przypadku korzystania z właściwości direct z zakresu ( bez kropki ) istnieje ryzyko ukrycia żądanej właściwości docelowej z nowo utworzoną w najbliższym górnym zakresie w łańcuchu prototypów podczas pisania do niej. Lepiej wyjaśnia to prezentacjaMyślę, że podejście Igora do tego, jak widać w cytacie, który podałeś, jest tylko wierzchołkiem góry lodowej o wiele większego problemu.
MVC i jego pochodne (MVP, PM, MVVM) są dobre i eleganckie w ramach jednego agenta, ale architektura serwer-klient jest do wszystkich celów systemem dwuagentowym, a ludzie często mają taką obsesję na punkcie tych wzorców, że zapominają o tym problem jest o wiele bardziej złożony. Próbując przestrzegać tych zasad, w rzeczywistości kończy się wadliwą architekturą.
Zróbmy to krok po kroku.
Wytyczne
Wyświetlenia
W kontekście Angular, widok jest DOM. Wytyczne są następujące:
Robić:
Nie:
Wygląda to równie kusząco, krótko i nieszkodliwie:
To prawie oznacza każdego programistę, który teraz, aby zrozumieć, jak działa system, musi sprawdzić zarówno pliki Javascript, jak i HTML.
Kontrolery
Robić:
Nie:
Powodem ostatniej wytycznej jest to, że kontrolerzy są siostrami poglądów, a nie bytami; ani nie są wielokrotnego użytku.
Można argumentować, że dyrektywy są wielokrotnego użytku, ale dyrektywy również są siostrzanymi widokami (DOM) - nigdy nie miały odpowiadać podmiotom.
Jasne, czasami widoki reprezentują byty, ale to raczej szczególny przypadek.
Innymi słowy, kontrolerzy powinni skupić się na prezentacji - jeśli zastosujesz logikę biznesową, nie tylko prawdopodobnie otrzymasz napompowany, mało zarządzalny kontroler, ale także naruszysz zasadę oddzielenia spraw .
W związku z tym kontrolery w Angular to bardziej model prezentacji lub MVVM .
A więc jeśli kontrolery nie powinny zajmować się logiką biznesową, kto powinien?
Co to jest model?
Twój model klienta jest często niepełny i nieaktualny
Jeśli nie piszesz aplikacji internetowej offline lub aplikacji, która jest strasznie prosta (kilka jednostek), Twój model klienta prawdopodobnie będzie:
Prawdziwy model musi trwać
W tradycyjnym MCV model jest jedyną rzeczą, którą się utrwala . Ilekroć mówimy o modelach, należy je kiedyś upierać. Twój klient może dowolnie manipulować modelami, ale dopóki podróż w obie strony do serwera nie zostanie zakończona pomyślnie, praca nie zostanie wykonana.
Konsekwencje
Dwa powyższe punkty powinny służyć jako przestroga - model, który posiada twój klient, może obejmować tylko częściową, przeważnie prostą logikę biznesową.
W związku z tym być może rozsądnie jest, w kontekście klienta, używać małych liter
M
- więc tak naprawdę jest to mVC , mVP i mVVm . DużyM
jest dla serwera.Logika biznesowa
Być może jedną z najważniejszych koncepcji dotyczących modeli biznesowych jest to, że można je podzielić na 2 typy (pomijam trzeci pogląd biznesowy, ponieważ jest to historia na inny dzień):
firstName
isirName
, metodę pobierającą, taką jak,getFullName()
można uznać za niezależną od aplikacji.Należy podkreślić, że obie te kwestie w kontekście klienta nie są „prawdziwą” logiką biznesową - zajmują się tylko tą częścią, która jest ważna dla klienta. Logika aplikacji (nie logika domeny) powinna odpowiadać za ułatwianie komunikacji z serwerem i większości interakcji użytkownika; podczas gdy logika domeny jest w dużej mierze niewielka, specyficzna dla jednostki i oparta na prezentacji.
Pozostaje pytanie - gdzie wrzucasz je w aplikacji kątowej?
Architektura 3 vs 4 warstwowa
Wszystkie te frameworki MVW używają 3 warstw:
W przypadku klientów są jednak dwa podstawowe problemy:
Alternatywą dla tej strategii jest strategia 4-warstwowa :
Prawdziwym problemem jest tutaj warstwa reguł biznesowych aplikacji (przypadki użycia), która często źle wpływa na klientów.
Warstwa ta jest realizowana przez interaktory (wujek Bob), co jest właściwie tym, co Martin Fowler nazywa warstwą usług skryptu operacyjnego .
Konkretny przykład
Rozważ następującą aplikację internetową:
Teraz powinno się wydarzyć kilka rzeczy:
Gdzie to wszystko rzucimy?
Jeśli twoja architektura obejmuje kontroler, który wywołuje
$resource
, wszystko to będzie się działo w kontrolerze. Ale jest lepsza strategia.Proponowane rozwiązanie
Poniższy diagram pokazuje, jak można rozwiązać powyższy problem, dodając kolejną warstwę logiki aplikacji w klientach Angular:
Więc dodajemy warstwę między kontrolerem do $ resource, tę warstwę (nazwijmy to interaktorem ):
UserInteractor
.I tak, z wymaganiami konkretnego przykładu powyżej:
validate()
validate()
metodę modelu .createUser()
źródło
Drobny problem w porównaniu ze świetnymi radami zawartymi w odpowiedzi Artema, ale jeśli chodzi o czytelność kodu, uznałem, że najlepiej jest zdefiniować API całkowicie wewnątrz
return
obiektu, aby zminimalizować przechodzenie tam iz powrotem w kodzie, aby sprawdzić, gdzie są zdefiniowane zmienne:Jeśli
return
obiekt zacznie wyglądać na „zbyt zatłoczony”, oznacza to, że Usługa robi za dużo.źródło
AngularJS nie implementuje MVC w tradycyjny sposób, raczej implementuje coś bliższego MVVM (Model-View-ViewModel), ViewModel można również nazwać binder (w przypadku kątowym może to być $ scope). Model -> Jak wiemy, model w angular może być po prostu zwykłymi, starymi obiektami JS lub danymi w naszej aplikacji
Widok -> widok w angularJS to kod HTML, który został przeanalizowany i skompilowany przez angularJS przez zastosowanie dyrektyw, instrukcji lub powiązań. Głównym punktem jest tutaj kątowe, dane wejściowe to nie tylko zwykły ciąg HTML (innerHTML), a raczej to DOM tworzony przez przeglądarkę.
ViewModel -> ViewModel jest w rzeczywistości łącznikiem / pomostem między widokiem a modelem w przypadku angularJS jest to $ scope, aby zainicjować i rozszerzyć kontroler $ scope, którego używamy.
Jeśli chcę podsumować odpowiedź: W aplikacji angularJS $ scope ma odniesienie do danych, Kontroler kontroluje zachowanie, a Widok obsługuje układ, współdziałając z kontrolerem, aby odpowiednio się zachowywał.
źródło
Aby uściślić to pytanie, Angular używa różnych wzorców projektowych, które już napotkaliśmy w naszym zwykłym programowaniu. 1) Kiedy rejestrujemy naszych kontrolerów lub dyrektywy, fabrykę, usługi itp. W odniesieniu do naszego modułu. Tutaj ukrywa dane z globalnej przestrzeni. Co to jest wzorzec modułu . 2) Kiedy angular używa swojego brudnego sprawdzania do porównywania zmiennych zakresu, tutaj używa wzorca obserwatora . 3) Wszystkie zakresy nadrzędne podrzędne w naszych kontrolerach używają wzorca prototypowego. 4) W przypadku wstrzykiwania usług używa wzorca fabrycznego .
Ogólnie rzecz biorąc, do rozwiązywania problemów wykorzystuje różne znane wzorce projektowe.
źródło