Um evento é uma notificação que descreve uma ocorrência do passado recente.
Uma implementação típica de um sistema orientado a eventos utiliza funções de expedidor e manipulador de eventos (ou assinantes ). O expedidor fornece uma API para conectar manipuladores até eventos (jQuery bind
) e um método para publicar um evento para seus assinantes ( trigger
no jQuery). Quando você está falando sobre eventos de IO ou UI, também costuma haver um loop de eventos , que detecta novos eventos como cliques do mouse e os passa para o expedidor. No JS-land, o dispatcher e o loop de eventos são fornecidos pelo navegador.
Para código que interage diretamente com o usuário - respondendo a pressionamentos de tecla e cliques - a programação orientada a eventos (ou uma variação dela, como a programação reativa funcional ) é quase inevitável. Você, o programador, não tem idéia de quando ou onde o usuário clicará, portanto, cabe à estrutura ou ao navegador da GUI detectar a ação do usuário em seu loop de eventos e notificar seu código. Esse tipo de infraestrutura também é usado em aplicativos de rede (cf NodeJS).
Seu exemplo, no qual você gera um evento em seu código, em vez de chamar uma função diretamente, tem algumas vantagens mais interessantes, as quais discutirei abaixo. A principal diferença é que o editor de um evento ( makeItSnow
) não especifica o destinatário da chamada; que está conectado em outro lugar (na chamada para bind
no seu exemplo). Isso é chamado de acenda e esqueça : makeItSnow
anuncia ao mundo que está nevando, mas não se importa com quem está ouvindo, o que acontece depois ou quando acontece - simplesmente transmite a mensagem e tira o pó das mãos.
Portanto, a abordagem orientada a eventos desacopla o remetente da mensagem do destinatário. Uma vantagem que isso oferece é que um determinado evento pode ter vários manipuladores. Você pode vincular uma gritRoads
função ao seu evento de neve sem afetar o shovelSnow
manipulador existente . Você tem flexibilidade na maneira como seu aplicativo é conectado; Para desativar um comportamento, basta remover a bind
chamada, em vez de procurar o código para encontrar todas as instâncias do comportamento.
Outra vantagem da programação orientada a eventos é que ela oferece um local para colocar preocupações transversais. O distribuidor de eventos desempenha o papel de Mediador , e algumas bibliotecas (como o Brighter ) utilizam um pipeline para que você possa conectar facilmente requisitos genéricos, como log ou qualidade de serviço.
Divulgação completa: O Brighter é desenvolvido na Huddle, onde trabalho.
Uma terceira vantagem de dissociar o remetente de um evento do receptor é que ele oferece flexibilidade quando você lida com o evento. Você pode processar cada tipo de evento em seu próprio encadeamento (se o seu expedidor de eventos o suportar) ou pode colocar eventos gerados em um intermediário de mensagens como o RabbitMQ e manipulá-los com um processo assíncrono ou até processá-los em massa durante a noite. O receptor do evento pode estar em um processo separado ou em uma máquina separada. Você não precisa alterar o código que gera o evento para fazer isso! Esta é a grande idéia por trás das arquiteturas de "microsserviço": serviços autônomos se comunicam usando eventos, com o middleware de mensagens como a espinha dorsal do aplicativo.
Para um exemplo bastante diferente de estilo orientado a eventos, consulte o design orientado a domínio , onde os eventos de domínio são usados para ajudar a manter agregados separados. Por exemplo, considere uma loja online que recomende produtos com base no seu histórico de compras. A Customer
precisa ter seu histórico de compras atualizado quando a ShoppingCart
é paga. O ShoppingCart
agregado pode notificar o evento Customer
criando um CheckoutCompleted
evento; o Customer
seria atualizado em uma transação separada em resposta ao evento.
A principal desvantagem desse modelo orientado a eventos é a indireção. Agora é mais difícil encontrar o código que lida com o evento porque você não pode simplesmente navegar para ele usando seu IDE; você precisa descobrir onde o evento está vinculado na configuração e esperar que você tenha encontrado todos os manipuladores. Há mais coisas para manter em sua mente a qualquer momento. As convenções de estilo de código podem ajudar aqui (por exemplo, colocar todas as chamadas bind
em um arquivo). Por uma questão de sanidade, é importante usar apenas um distribuidor de eventos e usá-lo de forma consistente.
Outra desvantagem é que é difícil refatorar eventos. Se você precisar alterar o formato de um evento, também precisará alterar todos os receptores. Isso é exacerbado quando os assinantes de um evento estão em máquinas diferentes, porque agora você precisa sincronizar as versões do software!
Em certas circunstâncias, o desempenho pode ser uma preocupação. Ao processar uma mensagem, o expedidor deve:
- Procure os manipuladores corretos em alguma estrutura de dados.
- Crie um pipeline de processamento de mensagens para cada manipulador. Isso pode envolver várias alocações de memória.
- Chame os manipuladores dinamicamente (possivelmente usando reflexão, se o idioma exigir).
Isso é certamente mais lento que uma chamada de função normal, que envolve apenas empurrar um novo quadro na pilha. No entanto, a flexibilidade oferecida por uma arquitetura orientada a eventos facilita muito o isolamento e a otimização do código lento. Ter a capacidade de enviar trabalho para um processador assíncrono é uma grande vitória aqui, pois permite atender a uma solicitação imediatamente enquanto o trabalho duro é tratado em segundo plano. De qualquer forma, se você estiver interagindo com o banco de dados ou desenhando coisas na tela, os custos de IO reduzirão totalmente os custos de processamento de uma mensagem. É um caso de evitar a otimização prematura.
Em resumo, os eventos são uma ótima maneira de criar software com pouca acoplagem, mas não são isentos de custos. Seria um erro, por exemplo, substituir cada chamada de função no seu aplicativo por um evento. Use eventos para criar divisões arquitetônicas significativas.
$(document).bind('snow', shovelShow)
. Não há necessidade de envolvê-lo em uma função anônima.