Programação orientada a eventos: quando vale a pena?

19

Ok, eu sei que o título desta pergunta é quase idêntico a Quando devo usar a programação baseada em eventos? mas as respostas dessa pergunta não me ajudaram a decidir se devo usar eventos no caso específico que estou enfrentando.

Estou desenvolvendo um pequeno aplicativo. É um aplicativo simples e, na maioria das vezes, sua funcionalidade é CRUD básica.

Após certos eventos (ao modificar certos dados), o aplicativo deve gravar uma cópia local dos dados em um arquivo. Não tenho certeza sobre qual é a melhor maneira de implementar isso. Eu posso:

  • Acione eventos quando os dados são modificados e vincule uma resposta (gere o arquivo) a esses eventos. Como alternativa, implemente o padrão observador. Parece complexidade desnecessária.
  • Chame o código de geração de arquivo diretamente do código que modifica os dados. Muito mais simples, mas parece errado que a dependência seja assim, ou seja, parece errado que a funcionalidade principal do aplicativo (código que modifica os dados) seja acoplada a esse benefício extra (código que gera um arquivo de backup). Eu sei, no entanto, que este aplicativo não evoluirá para um ponto em que esse acoplamento represente um problema.

Qual é a melhor abordagem neste caso?

abl
fonte
2
Eu diria que não implemente nada - basta usar um barramento de eventos existente. Isso vai tornar a vida muito mais simples ...
Boris the Spider
A programação orientada a eventos é inerentemente assíncrona. Os eventos podem ou não ocorrer, na ordem que você pretendia ou talvez em outra ordem ou não. Se você pode lidar com essa complexidade extra, vá em frente.
21316 Pieter B
Orientado a eventos normalmente significa que seu código é fornecido como retornos de chamada e estes são chamados de outros lugares de maneiras que você não pode prever. Sua descrição soa mais assim quando algo específico acontece no seu código, você precisa fazer mais do que uma implementação ingênua exigiria. Basta codificar as chamadas extras.
Thorbjørn Ravn Andersen 13/03
Não é uma diferença entre a eventos conduzido e baseado em eventos. Veja, por exemplo, o altamente instigante episódio do podcast .NET Rocks 355 com Ted Faison, Ted Faison leva os eventos ao limite! ( URL de download direto ) e o livro Programação baseada em eventos: levando os eventos ao limite .
Peter Mortensen
A entrevista com Ted Faison começa aos 13 minutos e 10 segundos.
Peter Mortensen

Respostas:

31

Siga o princípio do KISS: Mantenha-o simples, estúpido ou o princípio do YAGNI: você não precisará dele.

Você pode escrever o código como:

void updateSpecialData() {
    // do the update.
    backupData();
}

Ou você pode escrever um código como:

void updateSpecialData() {
     // do the update.
     emit SpecialDataUpdated();
}

void SpecialDataUpdatedHandler() {
     backupData();
}

void configureEventHandlers() {
     connect(SpecialDataUpdate, SpecialDataUpdatedHandler);
}

Na ausência de um motivo convincente para fazer o contrário, siga a rota mais simples. Técnicas como a manipulação de eventos são poderosas, mas aumentam a complexidade do seu código. Requer mais código para funcionar e dificulta o que acontece no seu código.

Os eventos são muito críticos na situação certa (imagine tentar fazer a programação da interface do usuário sem eventos!) Mas não os use quando puder usar o KISS ou o YAGNI.

Winston Ewert
fonte
Gosto especialmente do fato de que você mencionou que disparar eventos quando os dados são alterados não é trivial.
NoChance
13

O exemplo que você descreve de dados simples, nos quais a modificação aciona algum efeito, pode ser perfeitamente implementado com o padrão de design do observador :

  • isso é mais simples de implementar e manter do que o código completo orientado a eventos.
  • o acoplamento entre sujeito e observador pode ser abstrato, o que facilita a separação de preocupações.
  • é ideal para uma relação entre muitos (os sujeitos têm um ou muitos observadores).

A abordagem orientada a eventos vale a pena investir em cenários mais complexos, quando muitas interações diferentes podem ocorrer, em um contexto muitos-para-muitos, ou se são previstas reações em cadeia (por exemplo, um sujeito informa um observador, que em alguns casos deseja modificar o assunto ou outros assuntos)

Christophe
fonte
1
Estou confuso, o observador não é apenas uma maneira de implementar eventos?
svick
1
@ Rick Acho que não. Na programação orientada a eventos, você tem um loop principal que processa eventos em um relacionamento muitos para muitos, com remetentes e observadores dissociados. Penso que o observador pode contribuir processando um tipo de evento perticular, mas você não pode alcançar todo o espectro da EDP apenas com um observador. Eu acho que a confusão vem do fato de que em software orientado a eventos, os observadores são por vezes implementada no topo do processamento de eventos (tipicamente MVC com uma GUI)
Christophe
5

Como você diz, os eventos são uma ótima ferramenta para reduzir o acoplamento entre classes; portanto, embora possa envolver a criação de código adicional em alguns idiomas sem suporte interno para eventos, reduz a complexidade da imagem geral.

Os eventos são sem dúvida uma das ferramentas mais importantes no OO (de acordo com Alan Kay - os objetos se comunicam enviando e recebendo mensagens ). Se você usa um idioma que possui suporte interno para eventos ou trata funções como cidadãos de primeira classe, usá-los não é fácil.

Mesmo em idiomas sem suporte interno, a quantidade de clichê para algo como o padrão Observer é bastante mínima. Você pode encontrar uma biblioteca de eventos genéricos decente em algum lugar que possa ser usado em todos os seus aplicativos para minimizar o clichê. (Um agregador de eventos genérico ou mediador de eventos é útil em quase qualquer tipo de aplicativo).

Vale a pena em um aplicativo pequeno? Eu diria que sim definitivamente .

  • Manter as classes dissociadas entre si mantém o gráfico de dependência de classe limpo.
  • Classes sem nenhuma dependência concreta podem ser testadas isoladamente, sem consideração por outras classes nos testes.
  • Classes sem nenhuma dependência concreta exigem menos testes de unidade para uma cobertura completa.

Se você está pensando "Ah, mas é realmente apenas um aplicativo muito pequeno, isso não importa muito" , considere:

  • Às vezes, aplicativos pequenos acabam sendo combinados com aplicativos maiores posteriormente.
  • É provável que aplicativos pequenos incluam pelo menos alguma lógica ou componentes que possam precisar ser reutilizados posteriormente em outros aplicativos.
  • Os requisitos para aplicativos pequenos podem mudar, solicitando a necessidade de refatorar, o que é mais fácil quando o código existente é dissociado.
  • Recursos adicionais podem ser adicionados posteriormente, solicitando a necessidade de estender o código existente, o que também é muito mais fácil quando o código existente já está dissociado.
  • Código fracamente acoplado geralmente não leva muito mais tempo para escrever do que código fortemente acoplado; mas o código fortemente acoplado leva muito mais tempo para refatorar e testar do que o código pouco acoplado.

Em geral, o tamanho de um aplicativo não deve ser um fator decisivo para manter as classes fracamente acopladas; Os princípios do SOLID não são apenas para grandes aplicativos, são aplicáveis ​​a software e bases de código em qualquer escala.

De fato, o tempo economizado no teste unitário de suas classes de acoplamento fraco isoladamente deve contrabalançar qualquer tempo adicional gasto na dissociação dessas classes.

Ben Cottrell
fonte
2

O padrão de observador pode ser implementado de uma maneira muito menor do que o artigo da Wikipedia (ou o livro do GOF) o descreve, desde que suas linguagens de programação suportem algo como "retornos de chamada" ou "delegados". Basta passar um método de retorno de chamada para o seu código CRUD (o método observador, que pode ser um método genérico de "gravar em arquivo" ou um método vazio). Em vez de "acionamento de evento", basta chamar esse retorno de chamada.

O código resultante será apenas minimamente mais complexo do que chamar o código de geração de arquivo diretamente, mas sem as desvantagens do acoplamento rígido de componentes não relacionados.

Isso trará a você o "melhor dos dois mundos", sem sacrificar a dissociação de "YAGNI".

Doc Brown
fonte
Isso funciona na primeira vez que o Doc, mas quando o evento precisa acionar uma segunda coisa, é provável que a classe precise ser modificada dessa maneira. Se você usar um padrão de observador, poderá adicionar tantos comportamentos novos quanto desejar, sem abrir a classe original de volta para modificação.
precisa
2
@RubberDuck: o OP estava procurando uma solução "evitando complexidade desnecessária" - se houver eventos / comportamentos diferentes necessários, ele provavelmente não consideraria o padrão de observador muito complexo. Então, eu concordo com o que você disse, quando as coisas ficam mais complexas, um observador completo o serviria melhor, mas só então.
Doc Brown
Uma declaração justa, mas parece uma janela quebrada para mim.
precisa
2
@RubberDuck: adicionar um observador completo, com mecânica de editor / assinante "por precaução", quando não é realmente necessário, me parece uma superengenharia - isso não é melhor.
Doc Brown,
1
Não discordo que isso possa ter acabado com a engenharia. Provavelmente, sinto o que sinto porque é trivial de implementar na minha pilha de opções. Enfim, concordaremos em discordar?
precisa