É tudo uma questão de acoplamento fraco e responsabilidade única, o que anda de mãos dadas com os padrões MV * (MVC / MVP / MVVM) em JavaScript que são muito modernos nos últimos anos.
O acoplamento fraco é um princípio orientado a objetos no qual cada componente do sistema conhece sua responsabilidade e não se preocupa com os outros componentes (ou pelo menos tenta não se importar com eles o máximo possível). O acoplamento fraco é uma coisa boa porque você pode reutilizar facilmente os diferentes módulos. Você não está acoplado às interfaces de outros módulos. Usando publicar / assinar, você só estará conectado à interface de publicar / assinar, o que não é um grande problema - apenas dois métodos. Então, se você decidir reutilizar um módulo em um projeto diferente, você pode apenas copiar e colar e provavelmente funcionará ou pelo menos você não precisará de muito esforço para fazê-lo funcionar.
Ao falar sobre acoplamento fraco, devemos mencionar a separação de interesses. Se você estiver construindo um aplicativo usando um padrão de arquitetura MV *, você sempre terá um Modelo (s) e uma Visão (ões). O modelo é a parte comercial do aplicativo. Você pode reutilizá-lo em diferentes aplicativos, portanto, não é uma boa ideia combiná-lo com a Visualização de um único aplicativo, onde deseja mostrá-lo, porque geralmente nos diferentes aplicativos você tem visualizações diferentes. Portanto, é uma boa ideia usar publicar / assinar para a comunicação Model-View. Quando seu Model muda, ele publica um evento, a View o captura e se atualiza. Você não tem nenhuma sobrecarga de publicação / assinatura, isso ajuda você no desacoplamento. Da mesma maneira, você pode manter a lógica da sua aplicação no Controlador, por exemplo (MVVM, MVP não é exatamente um Controlador) e manter a Visualização o mais simples possível. Quando sua View muda (ou o usuário clica em algo, por exemplo), ela apenas publica um novo evento, o Controller o pega e decide o que fazer. Se você estiver familiarizado com oPadrão MVC ou com MVVM em tecnologias Microsoft (WPF / Silverlight), você pode pensar em publicar / assinar como o padrão Observer . Essa abordagem é usada em estruturas como Backbone.js, Knockout.js (MVVM).
Aqui está um exemplo:
//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);
}
}
}());
Outro exemplo. Se você não gosta da abordagem MV *, pode usar algo um pouco diferente (há uma interseção entre o que vou descrever a seguir e o último mencionado). Basta estruturar sua aplicação em diferentes módulos. Por exemplo, veja o Twitter.
Se você olhar para a interface, simplesmente terá caixas diferentes. Você pode pensar em cada caixa como um módulo diferente. Por exemplo, você pode postar um tweet. Esta ação requer a atualização de alguns módulos. Em primeiro lugar, ele deve atualizar seus dados de perfil (caixa superior esquerda), mas também deve atualizar sua linha do tempo. Claro, você pode manter referências a ambos os módulos e atualizá-los separadamente usando sua interface pública, mas é mais fácil (e melhor) apenas publicar um evento. Isso tornará a modificação de seu aplicativo mais fácil devido ao acoplamento mais fraco. Se você desenvolver um novo módulo que dependa de novos tweets, você pode apenas se inscrever no evento “publicar-tweet” e lidar com isso. Essa abordagem é muito útil e pode tornar seu aplicativo muito desacoplado. Você pode reutilizar seus módulos com muita facilidade.
Aqui está um exemplo básico da última abordagem (este não é o código original do Twitter, é apenas uma amostra minha):
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);
});
}
};
}());
Para essa abordagem, há uma excelente palestra de Nicholas Zakas . Para a abordagem MV *, os melhores artigos e livros que conheço são publicados por Addy Osmani .
Desvantagens: você deve ter cuidado com o uso excessivo de publicar / assinar. Se você tem centenas de eventos, pode ficar muito confuso gerenciar todos eles. Você também pode ter colisões se não estiver usando namespacing (ou não da maneira certa). Uma implementação avançada do Mediator que se parece muito com uma publicação / assinatura pode ser encontrada aqui https://github.com/ajacksified/Mediator.js . Ele tem namespacing e recursos como “bubbling” de eventos que, é claro, podem ser interrompidos. Outra desvantagem de publicar / assinar é o teste de unidade rígido, pode ser difícil isolar as diferentes funções nos módulos e testá-los independentemente.
O principal objetivo é reduzir o acoplamento entre o código. É uma forma de pensar um tanto baseada em eventos, mas os "eventos" não estão ligados a um objeto específico.
Vou escrever um grande exemplo abaixo em algum pseudocódigo que se parece um pouco com JavaScript.
Digamos que temos uma classe Radio e uma classe Relay:
Sempre que o rádio recebe um sinal, queremos vários relés para retransmitir a mensagem de alguma forma. O número e os tipos de relés podem ser diferentes. Podemos fazer assim:
Isso funciona bem. Mas agora imagine que queremos um componente diferente para também fazer parte dos sinais que a classe Rádio recebe, a saber: Alto-falantes:
(desculpe se as analogias não são excelentes ...)
Poderíamos repetir o padrão novamente:
Poderíamos tornar isso ainda melhor criando uma interface, como "SignalListener", de modo que só precisemos de uma lista na classe Radio e sempre possamos chamar a mesma função em qualquer objeto que tenhamos que deseja ouvir o sinal. Mas isso ainda cria um acoplamento entre qualquer interface / classe base / etc que decidirmos e a classe Radio. Basicamente, sempre que você muda uma das classes Radio, Signal ou Relay, você tem que pensar sobre como isso poderia afetar as outras duas classes.
Agora vamos tentar algo diferente. Vamos criar uma quarta classe chamada RadioMast:
Agora temos um padrão que conhecemos e podemos usá-lo para qualquer número e tipo de classes, desde que:
Portanto, mudamos a classe Radio para sua forma simples e final:
E adicionamos os alto-falantes e o relé à lista de receptores do RadioMast para este tipo de sinal:
Agora, a classe Speakers and Relay tem conhecimento zero de qualquer coisa, exceto que eles têm um método que pode receber um sinal, e a classe Radio, sendo a editora, está ciente do RadioMast para o qual publica sinais. Este é o ponto de usar um sistema de passagem de mensagens como publicar / assinar.
fonte
class
palavra - chave. Por favor, enfatize este fato, por exemplo. classificando seu código como pseudo-código.As outras respostas fizeram um ótimo trabalho ao mostrar como o padrão funciona. Eu queria abordar a questão implícita "o que há de errado com o jeito antigo? ", Pois tenho trabalhado com esse padrão recentemente e acho que envolve uma mudança em meu pensamento.
Imagine que assinamos um boletim econômico. O boletim publica uma manchete: " Baixe o Dow Jones em 200 pontos ". Seria uma mensagem estranha e um tanto irresponsável de enviar. Se, no entanto, publicou: "A Enron entrou com pedido de proteção contra falência, capítulo 11, esta manhã ", então esta é uma mensagem mais útil. Observe que a mensagem pode fazer com que o Dow Jones caia 200 pontos, mas isso é outro assunto.
Há uma diferença entre enviar um comando e avisar sobre algo que acabou de acontecer. Com isso em mente, pegue sua versão original do padrão pub / sub, ignorando o manipulador por enquanto:
Já existe um forte acoplamento implícito aqui, entre a ação do usuário (um clique) e a resposta do sistema (um pedido sendo removido). Efetivamente em seu exemplo, a ação é dar um comando. Considere esta versão:
Agora, o manipulador está respondendo a algo de interesse que aconteceu, mas não tem obrigação de remover um pedido. Na verdade, o manipulador pode fazer todo tipo de coisa não diretamente relacionada à remoção de um pedido, mas ainda pode ser relevante para a ação de chamada. Por exemplo:
A distinção entre um comando e uma notificação é útil para fazer com este padrão, IMO.
fonte
remindUserToFloss
&increaseProgrammerBrowniePoints
) estivessem localizadas em módulos separados, você publicaria 2 eventos, um logo após o outro,handleRemoveOrderRequest
ouflossModule
publicaria um evento em umbrowniePoints
módulo quandoremindUserToFloss()
terminar?Para que você não precise codificar chamadas de método / função, basta publicar o evento sem se importar com quem ouve. Isso torna o editor independente do assinante, reduzindo a dependência (ou acoplamento, qualquer termo que você preferir) entre 2 partes diferentes do aplicativo.
Aqui estão algumas desvantagens do acoplamento, conforme mencionado pela wikipedia
Considere algo como um objeto encapsulando dados de negócios. Ele tem uma chamada de método codificado para atualizar a página sempre que a idade é definida:
Agora não posso testar o objeto pessoa sem incluir também a
showAge
função. Além disso, se eu precisar mostrar a idade em algum outro módulo de GUI também, preciso codificar essa chamada de método.setAge
e agora há dependências para 2 módulos não relacionados no objeto pessoa. Também é difícil manter quando você vê essas chamadas sendo feitas e elas nem mesmo estão no mesmo arquivo.Observe que, dentro do mesmo módulo, você pode, é claro, ter chamadas de método diretas. Mas os dados de negócios e o comportamento superficial da interface do usuário não devem residir no mesmo módulo por nenhum padrão razoável.
fonte
removeOrder
existe, então você não pode depender dele. No segundo exemplo, você tem que saber.A implementação do PubSub é comumente vista onde há -
Código de exemplo -
fonte
O artigo "As Muitas Faces de Publicar / Assinar" é uma boa leitura e uma coisa que eles enfatizam é o desacoplamento em três "dimensões". Aqui está meu resumo bruto, mas por favor, consulte o artigo também.
fonte
Resposta simples A pergunta original procurava uma resposta simples. Aqui está minha tentativa.
Javascript não fornece nenhum mecanismo para que objetos de código criem seus próprios eventos. Portanto, você precisa de um tipo de mecanismo de evento. o padrão Publicar / assinar atenderá a essa necessidade e cabe a você escolher o mecanismo que melhor atende às suas necessidades.
Agora podemos ver a necessidade do padrão pub / sub, então você prefere lidar com eventos DOM de forma diferente de como você lida com seus eventos pub / sub? Para reduzir a complexidade e outros conceitos, como separação de interesses (SoC), você pode ver o benefício de tudo ser uniforme.
Então, paradoxalmente, mais código cria uma melhor separação de interesses, que pode ser escalada para páginas da web muito complexas.
Espero que alguém considere esta discussão boa o suficiente, sem entrar em detalhes.
fonte