Chodzi o luźne powiązanie i pojedynczą odpowiedzialność, która idzie w parze z wzorcami MV * (MVC / MVP / MVVM) w JavaScript, które są bardzo nowoczesne w ciągu ostatnich kilku lat.
Luźne powiązanie to zasada zorientowana obiektowo, w której każdy komponent systemu zna swoją odpowiedzialność i nie dba o inne komponenty (lub przynajmniej stara się nie przejmować nimi tak bardzo, jak to możliwe). Luźne połączenie to dobra rzecz, ponieważ można łatwo ponownie wykorzystać różne moduły. Nie jesteś połączony z interfejsami innych modułów. Używając publikuj / subskrybuj, łączysz się tylko z interfejsem publikuj / subskrybuj, co nie jest wielkim problemem - tylko dwie metody. Więc jeśli zdecydujesz się ponownie użyć modułu w innym projekcie, możesz go po prostu skopiować i wkleić, a prawdopodobnie zadziała lub przynajmniej nie będziesz potrzebować dużo wysiłku, aby działał.
Mówiąc o luźnym sprzężeniu należy wspomnieć o rozdzieleniu obaw. Jeśli tworzysz aplikację przy użyciu wzorca architektonicznego MV *, zawsze masz Model (y) i Widok (y). Model jest biznesową częścią aplikacji. Możesz go ponownie wykorzystać w różnych aplikacjach, więc nie jest dobrym pomysłem łączenie go z widokiem pojedynczej aplikacji, w której chcesz go wyświetlić, ponieważ zwykle w różnych aplikacjach masz różne widoki. Dlatego dobrym pomysłem jest użycie publikowania / subskrypcji do komunikacji Model-View. Gdy model się zmieni, publikuje zdarzenie, widok przechwytuje je i sam się aktualizuje. Nie masz żadnych kosztów związanych z publikacją / subskrypcją, pomaga to w oddzieleniu. W ten sam sposób możesz na przykład zachować logikę aplikacji w kontrolerze (MVVM, MVP to nie jest dokładnie kontroler) i utrzymywać widok tak prosty, jak to tylko możliwe. Kiedy Twój Widok się zmieni (lub na przykład użytkownik kliknie coś), po prostu opublikuje nowe zdarzenie, Kontroler przechwytuje je i decyduje, co zrobić. Jeśli znaszWzorzec MVC lub z MVVM w technologiach firmy Microsoft (WPF / Silverlight) można myśleć o publikacji / subskrypcji jak o wzorcu obserwatora . To podejście jest używane w frameworkach takich jak Backbone.js, Knockout.js (MVVM).
Oto przykład:
//Model
function Book(name, isbn) {
this.name = name;
this.isbn = isbn;
}
function BookCollection(books) {
this.books = books;
}
BookCollection.prototype.addBook = function (book) {
this.books.push(book);
$.publish('book-added', book);
return book;
}
BookCollection.prototype.removeBook = function (book) {
var removed;
if (typeof book === 'number') {
removed = this.books.splice(book, 1);
}
for (var i = 0; i < this.books.length; i += 1) {
if (this.books[i] === book) {
removed = this.books.splice(i, 1);
}
}
$.publish('book-removed', removed);
return removed;
}
//View
var BookListView = (function () {
function removeBook(book) {
$('#' + book.isbn).remove();
}
function addBook(book) {
$('#bookList').append('<div id="' + book.isbn + '">' + book.name + '</div>');
}
return {
init: function () {
$.subscribe('book-removed', removeBook);
$.subscribe('book-aded', addBook);
}
}
}());
Inny przykład. Jeśli nie podoba ci się podejście MV *, możesz użyć czegoś nieco innego (istnieje skrzyżowanie między tym, które opiszę dalej, a ostatnim wymienionym). Po prostu podziel swoją aplikację na różne moduły. Na przykład spójrz na Twittera.
Jeśli spojrzysz na interfejs, po prostu masz różne pola. Możesz myśleć o każdym pudełku jako o innym module. Na przykład możesz opublikować tweet. Ta akcja wymaga aktualizacji kilku modułów. Po pierwsze musi zaktualizować dane Twojego profilu (lewe górne pole), ale musi również zaktualizować Twoją oś czasu. Oczywiście możesz zachować odniesienia do obu modułów i aktualizować je osobno, używając ich publicznego interfejsu, ale łatwiej (i lepiej) jest po prostu opublikować wydarzenie. Ułatwi to modyfikację aplikacji ze względu na luźniejsze powiązania. Jeśli tworzysz nowy moduł, który zależy od nowych tweetów, możesz po prostu zapisać się na wydarzenie „publikuj-tweet” i obsłużyć. Takie podejście jest bardzo przydatne i może sprawić, że aplikacja będzie bardzo rozdzielona. Możesz bardzo łatwo ponownie wykorzystać swoje moduły.
Oto podstawowy przykład ostatniego podejścia (to nie jest oryginalny kod twittera, to tylko przykład mojego autorstwa):
var Twitter.Timeline = (function () {
var tweets = [];
function publishTweet(tweet) {
tweets.push(tweet);
//publishing the tweet
};
return {
init: function () {
$.subscribe('tweet-posted', function (data) {
publishTweet(data);
});
}
};
}());
var Twitter.TweetPoster = (function () {
return {
init: function () {
$('#postTweet').bind('click', function () {
var tweet = $('#tweetInput').val();
$.publish('tweet-posted', tweet);
});
}
};
}());
Dla tego podejścia jest doskonała przemowa Nicholasa Zakasa . Jeśli chodzi o podejście MV *, najlepsze znane mi artykuły i książki są publikowane przez Addy Osmani .
Wady: musisz uważać na nadmierne wykorzystanie opcji publikowania / subskrybowania. Jeśli masz setki wydarzeń, zarządzanie nimi wszystkimi może być bardzo skomplikowane. Możesz także mieć kolizje, jeśli nie używasz przestrzeni nazw (lub nie używasz jej we właściwy sposób). Zaawansowaną implementację Mediatora, która wygląda podobnie do publikacji / subskrypcji, można znaleźć tutaj https://github.com/ajacksified/Mediator.js . Ma przestrzeń nazw i funkcje takie jak „bulgotanie” zdarzeń, które oczywiście można przerwać. Inną wadą publikowania / subskrypcji jest trudne testowanie jednostkowe, może być trudne wyodrębnienie różnych funkcji w modułach i przetestowanie ich niezależnie.
Głównym celem jest zmniejszenie sprzężenia między kodem. Jest to sposób myślenia w pewnym stopniu oparty na zdarzeniach, ale „zdarzenia” nie są związane z konkretnym obiektem.
Napiszę poniżej duży przykład w pseudokodzie, który wygląda trochę jak JavaScript.
Powiedzmy, że mamy klasę Radio i klasę przekaźnika:
Zawsze, gdy radio odbiera sygnał, chcemy, aby pewna liczba przekaźników w jakiś sposób przekazywała wiadomość. Liczba i typy przekaźników mogą się różnić. Moglibyśmy to zrobić tak:
To działa dobrze. Ale teraz wyobraźmy sobie, że chcemy, aby inny komponent również brał udział w sygnałach odbieranych przez klasę Radio, a mianowicie Głośniki:
(przepraszam, jeśli analogie nie są na najwyższym poziomie ...)
Możemy powtórzyć wzór:
Moglibyśmy to jeszcze ulepszyć, tworząc interfejs, taki jak „SignalListener”, dzięki czemu potrzebujemy tylko jednej listy w klasie Radio i zawsze możemy wywołać tę samą funkcję na jakimkolwiek obiekcie, który mamy, który chce słuchać sygnału. Ale to nadal tworzy sprzężenie między dowolnym interfejsem / klasą bazową / etc, na który zdecydujemy się, a klasą Radio. Zasadniczo za każdym razem, gdy zmieniasz jedną z klas Radio, Signal lub Relay, musisz pomyśleć o tym, jak może to wpłynąć na pozostałe dwie klasy.
Teraz spróbujmy czegoś innego. Utwórzmy czwartą klasę o nazwie RadioMast:
Teraz mamy wzorzec , którego jesteśmy świadomi i możemy go użyć dla dowolnej liczby i typów klas, o ile:
Więc zmieniamy klasę Radio na ostateczną, prostą formę:
I dodajemy głośniki i przekaźnik do listy odbiorników RadioMast dla tego typu sygnału:
Teraz klasa Speakers and Relay nie ma żadnej wiedzy na temat czegokolwiek poza tym, że ma metodę, która może odebrać sygnał, a klasa Radio, będąc wydawcą, jest świadoma, że RadioMast publikuje sygnały. To jest punkt używania systemu przekazywania wiadomości, takiego jak publikuj / subskrybuj.
źródło
class
słowa kluczowego. Prosimy o podkreślenie tego faktu np. klasyfikując swój kod jako pseudokod.Inne odpowiedzi wykonały świetną robotę, pokazując, jak działa wzór. Chciałem odpowiedzieć na sugerowane pytanie „ co jest nie tak ze starym sposobem? ”, Ponieważ ostatnio pracowałem z tym wzorcem i stwierdziłem, że wiąże się to ze zmianą w moim myśleniu.
Wyobraź sobie, że subskrybowaliśmy biuletyn ekonomiczny. Biuletyn publikuje nagłówek: „ Obniż Dow Jones o 200 punktów ”. To byłaby dziwna i nieco nieodpowiedzialna wiadomość. Jeśli jednak opublikował: „ Enron złożył dziś rano wniosek o ochronę przed bankructwem na mocy rozdziału 11 ”, to jest to bardziej przydatna wiadomość. Zauważ, że wiadomość może spowodować spadek Dow Jones o 200 punktów, ale to już inna sprawa.
Istnieje różnica między wysłaniem polecenia a powiadomieniem o czymś, co właśnie się wydarzyło. Mając to na uwadze, weź oryginalną wersję wzorca pub / sub, na razie ignorując procedurę obsługi:
Istnieje już tutaj domniemane silne powiązanie między działaniem użytkownika (kliknięciem) a odpowiedzią systemu (usunięcie zlecenia). Skutecznie w twoim przykładzie, akcja jest wydaniem polecenia. Rozważ tę wersję:
Teraz opiekun reaguje na coś interesującego, co się wydarzyło, ale nie ma obowiązku usunięcia zamówienia. W rzeczywistości program obsługi może robić różne rzeczy, które nie są bezpośrednio związane z usunięciem zamówienia, ale nadal mogą być związane z akcją wywołującą. Na przykład:
Rozróżnienie między poleceniem a powiadomieniem jest przydatne do zrobienia tego wzorca, IMO.
źródło
remindUserToFloss
&increaseProgrammerBrowniePoints
) znajdowały się w oddzielnych modułach, czy opublikowałbyś 2 zdarzenia, jedno po drugim, w tym miejscu,handleRemoveOrderRequest
czy teżflossModule
opublikowałbyś wydarzenie wbrowniePoints
module po zakończeniuremindUserToFloss()
?Aby nie trzeba było na stałe kodować wywołań metod / funkcji, po prostu publikujesz zdarzenie bez dbania o to, kto słucha. To uniezależnia wydawcę od subskrybenta, zmniejszając zależność (lub łączenie, jakkolwiek wolisz) między 2 różnymi częściami aplikacji.
Oto kilka wad sprzężenia, o których wspomina wikipedia
Rozważmy coś w rodzaju obiektu hermetyzującego dane biznesowe. Ma zakodowane wywołanie metody, która aktualizuje stronę po ustawieniu wieku:
Teraz nie mogę przetestować obiektu osoby bez uwzględnienia
showAge
funkcji. Ponadto, jeśli chcę pokazać wiek również w innym module GUI, muszę zakodować wywołanie tej metody.setAge
, a teraz istnieją zależności dla 2 niepowiązanych modułów w obiekcie osoby. Jest to również trudne do utrzymania, gdy widzisz nawiązywane połączenia i nie ma ich nawet w tym samym pliku.Zauważ, że w tym samym module możesz oczywiście mieć bezpośrednie wywołania metod. Jednak dane biznesowe i powierzchowne zachowanie GUI nie powinny znajdować się w tym samym module według jakichkolwiek rozsądnych standardów.
źródło
removeOrder
ogóle istnieje, więc nie możesz być od niego zależny. W drugim przykładzie musisz wiedzieć.Implementacja PubSub jest często spotykana tam, gdzie jest -
Przykładowy kod -
źródło
Artykuł „Wiele twarzy publikowania / subskrybowania” jest dobrą lekturą i jedną z rzeczy, na którą kładą nacisk, jest rozdzielenie w trzech „wymiarach”. Oto moje surowe podsumowanie, ale proszę również odwołać się do artykułu.
źródło
Prosta odpowiedź Pierwotne pytanie dotyczyło prostej odpowiedzi. Oto moja próba.
JavaScript nie zapewnia żadnego mechanizmu dla obiektów kodu do tworzenia własnych zdarzeń. Potrzebujesz więc pewnego rodzaju mechanizmu zdarzeń. wzorzec publikowania / subskrypcji odpowiada na tę potrzebę, a wybór mechanizmu, który najlepiej odpowiada Twoim potrzebom, zależy od Ciebie.
Teraz widzimy potrzebę wzorca pub / sub, czy wolałbyś więc obsługiwać zdarzenia DOM inaczej niż w przypadku zdarzeń pub / sub? W celu zmniejszenia złożoności i innych koncepcji, takich jak oddzielenie problemów (SoC), możesz dostrzec korzyści płynące z jednolitości wszystkiego.
Tak więc, paradoksalnie, więcej kodu zapewnia lepszą separację problemów, co dobrze skaluje się nawet do bardzo złożonych stron internetowych.
Mam nadzieję, że ktoś uzna to za wystarczająco dobrą dyskusję bez wchodzenia w szczegóły.
źródło