Quando devo usar a programação baseada em eventos?


65

Tenho passado retornos de chamada ou apenas acionado as funções de outras funções nos meus programas para que as coisas aconteçam após a conclusão das tarefas. Quando algo termina, eu aciono a função diretamente:

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    shovelSnow();
}

Mas eu li sobre muitas estratégias diferentes de programação, e uma que eu entendo ser poderosa, mas que ainda não pratiquei, é baseada em eventos (acho que um método que li sobre foi chamado "pub-sub" ):

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    $(document).trigger('snow');
}

$(document).bind('snow', shovelSnow);

Gostaria de entender os pontos fortes e fracos objetivos da programação baseada em eventos, em vez de chamar todas as suas funções de outras funções. Em quais situações de programação a programação baseada em eventos faz sentido?


2
Como um aparte, você pode apenas usar $(document).bind('snow', shovelShow). Não há necessidade de envolvê-lo em uma função anônima.
Karl Bielefeldt

4
Você também pode estar interessado em aprender sobre "programação reativa", que tem muito em comum com a programação orientada a eventos.
Eric Lippert 01/01

Respostas:


75

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 ( triggerno 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 bindno seu exemplo). Isso é chamado de acenda e esqueça : makeItSnowanuncia 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 gritRoadsfunção ao seu evento de neve sem afetar o shovelSnowmanipulador existente . Você tem flexibilidade na maneira como seu aplicativo é conectado; Para desativar um comportamento, basta remover a bindchamada, 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 Customerprecisa ter seu histórico de compras atualizado quando a ShoppingCarté paga. O ShoppingCartagregado pode notificar o evento Customercriando um CheckoutCompletedevento; o Customerseria 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 bindem 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:

  1. Procure os manipuladores corretos em alguma estrutura de dados.
  2. Crie um pipeline de processamento de mensagens para cada manipulador. Isso pode envolver várias alocações de memória.
  3. 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.


2
Esta resposta diz o mesmo que a resposta do 5377 que eu selecionei como correta; Estou mudando minha seleção para marcar esta porque ela é mais detalhada.
Viziionary

11
A velocidade é uma desvantagem significativa do código orientado a eventos? Parece que poderia ser, mas eu não sei bem.
precisa saber é o seguinte

11
@ raptortech97 certamente pode ser. Para o código que precisa ser particularmente rápido, você provavelmente desejaria evitar o envio de eventos em um loop interno; felizmente, nessas situações, geralmente é bem definido o que você precisa fazer, para que você não precise de flexibilidade extra de eventos (ou publicação / assinatura ou observadores, que são mecanismos equivalentes com terminologia diferente).
Jules

11
Observe também que existem alguns idiomas (por exemplo, Erlang) criados em torno do modelo do ator, onde tudo é mensagens (eventos). Nesse caso, o compilador pode decidir se deseja implementar as mensagens / eventos como chamadas diretas de função ou como comunicação.
Brendan

11
Para "desempenho", acho que precisamos distinguir entre desempenho de thread único e escalabilidade. Mensagens / eventos podem ser piores para o desempenho de thread único (mas podem ser convertidos em chamadas de funções a custo zero zero e não são piores), e para a escalabilidade é superior em praticamente todos os aspectos (por exemplo, provavelmente resultará em melhorias maciças de desempenho no multi -CPU e futuros sistemas com "muitas CPUs").
Brendan

25

A programação baseada em eventos é usada quando o programa não controla a sequência de eventos que ele executa. Em vez disso, o fluxo do programa é direcionado por um processo externo, como um usuário (por exemplo, GUI), outro sistema (por exemplo, cliente / servidor) ou outro processo (por exemplo, RPC).

Por exemplo, um script de processamento em lote sabe o que precisa fazer, apenas o faz. É não baseada em eventos.

Um processador de texto fica lá e aguarda o usuário começar a digitar. As teclas pressionadas são eventos que acionam a funcionalidade para atualizar o buffer interno do documento. O programa não pode saber o que você deseja digitar; portanto, ele deve ser orientado a eventos.

A maioria dos programas da GUI é orientada a eventos porque eles são criados com base na interação do usuário. No entanto, os programas baseados em eventos não se limitam às GUIs, que é simplesmente o exemplo mais familiar para a maioria das pessoas. Os servidores Web esperam que os clientes se conectem e sigam um idioma semelhante. Os processos em segundo plano no seu computador também podem responder a eventos. Por exemplo, um antivírus sob demanda pode receber um evento do sistema operacional referente a um arquivo recém-criado ou atualizado e, em seguida, verificar esse arquivo em busca de vírus.


18

Em um aplicativo baseado em evento, o conceito de ouvintes de eventos oferece a capacidade de escrever ainda mais aplicativos com acoplamento fraco .

Por exemplo, um módulo ou plug-in de terceiros pode excluir um registro do banco de dados e, em seguida, acionar o receordDeletedevento e deixar o resto para os ouvintes do evento fazerem seu trabalho. Tudo funcionará bem, mesmo que o módulo acionador nem saiba quem está ouvindo esse evento em particular ou o que deve acontecer a seguir.


6

Uma analogia simples que eu gostaria de acrescentar que me ajudou:

Pense nos componentes (ou objetos) do seu aplicativo como um grande grupo de amigos do Facebook.

Quando um de seus amigos quer lhe contar uma coisa, eles podem ligar diretamente para você ou publicá-la no mural do Facebook. Quando eles publicam no Facebook, qualquer pessoa pode vê-lo e reagir, mas muitas pessoas não. Às vezes, é algo importante que as pessoas provavelmente precisam reagir a ela, como "Estamos tendo um bebê!" ou "A banda mais ou menos está fazendo um show surpresa no bar Drunkin 'Clam!". No último caso, os outros amigos provavelmente precisarão reagir a isso, principalmente se estiverem interessados ​​nessa banda.

Se seu amigo quiser manter um segredo entre você e ele, provavelmente não o publicará no mural do Facebook, ligará diretamente para você e lhe contará. Imagine um cenário em que você diz a uma garota que gosta de encontrá-la em um restaurante para um encontro. Em vez de ligar diretamente para ela e perguntar, você a publica no mural do Facebook para que todos os seus amigos vejam. Isso funciona, mas se você tem uma ex ciumenta, ela pode ver isso e aparecer no restaurante para estragar o seu dia.

Ao decidir se deve ou não criar ouvintes de eventos para implementar algo, pense nessa analogia. Esse componente precisa divulgar seus negócios para qualquer um ver? Ou eles precisam ligar para alguém diretamente? As coisas podem ficar confusas com bastante facilidade, por isso tome cuidado.


0

Essa analogia a seguir pode ajudá-lo a entender a programação de E / S orientada a eventos desenhando uma linha paralela à de espera na recepção do médico.

Bloquear E / S é como, se você estiver na fila, a recepcionista pede a um cara na sua frente para preencher o formulário e ela espera até que ele termine. Você tem que esperar sua vez até que o cara termine sua forma, isso está bloqueando.

Se um cara leva 3 minutos para preencher, o 10º cara tem que esperar até 30 minutos. Agora, para reduzir esse 10º tempo de espera, a solução seria aumentar o número de recepcionistas, o que é caro. É o que acontece nos servidores da web tradicionais. Se você solicitar informações do usuário, a solicitação subsequente de outros usuários deverá aguardar a conclusão da operação atual, buscando no Banco de Dados. Isso aumenta o "tempo de resposta" da 10ª solicitação e aumenta exponencialmente para o enésimo usuário. Para evitar isso, os servidores da Web tradicionais criam um encadeamento (equivalente ao número crescente de recepcionistas) para cada solicitação única, ou seja, basicamente cria uma cópia do servidor para cada solicitação, o que representa um custo caro do consumo de CPU, pois cada solicitação precisará de um sistema operacional. fio. Para ampliar o aplicativo,

Orientado a Eventos : A outra abordagem para aumentar o "tempo de resposta" da fila é optar pela abordagem orientada a eventos, onde o pessoal da fila será entregue ao formulário, solicitado a preencher e retornar após a conclusão. Portanto, a recepcionista sempre pode atender a solicitação. É exatamente isso que o javascript tem feito desde o início. No navegador, o javascript responderia ao evento de clique do usuário, rolagem, furto ou busca no banco de dados e assim por diante. Isso é possível no javascript inerentemente, porque o javascript trata as funções como objetos de primeira classe e pode ser passado como parâmetro para outras funções (chamadas de retorno de chamada) e pode ser chamado na conclusão de uma tarefa específica. É exatamente isso que o node.js faz no servidor. Você pode encontrar mais informações sobre programação orientada a eventos e bloqueio de E / S, no contexto do nó aqui

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.