Ao projetar uma API que fornece uma interface de escuta de eventos, parece haver duas maneiras conflitantes de tratar chamadas para adicionar / remover ouvintes:
Várias chamadas para addListener adicionarão apenas um único ouvinte (como adicioná-lo a um conjunto); pode ser removido com uma única chamada para removeListener.
Várias chamadas para addListener adicionarão um ouvinte a cada vez (como adicioná-lo a uma lista); deve ser equilibrado por várias chamadas para removeListener.
Encontrei um exemplo de cada um: (1) - A chamada DOM addEventListener nos navegadores apenas adiciona ouvintes uma vez, ignorando silenciosamente as solicitações para adicionar o mesmo ouvinte pela segunda vez e (2) - o .on
comportamento do jQuery adiciona ouvintes várias vezes .
A maioria das outras APIs de ouvintes parece usar (2), como ouvintes de eventos SWT e Swing. Se (1) for escolhido, há também a questão de saber se deve falhar silenciosamente ou com um erro quando houver uma solicitação para adicionar o mesmo ouvinte duas vezes.
Nas minhas implementações, costumo seguir com (2), uma vez que fornece uma interface mais limpa do tipo configuração / desmontagem e revela bugs em que a 'instalação' é feita acidentalmente duas vezes e é consistente com a maioria das implementações que já vi.
Isso me leva à minha pergunta - Existe uma arquitetura específica ou outro design subjacente que se presta melhor à outra implementação? (ou seja: por que o outro padrão existe?)
fonte
addListener(foo); addListener(foo); addListener(bar);
. O seu caso nº 1 adiciona umfoo
e umbar
, ou apenasbar
(ou seja,bar
sobrescrevefoo
como ouvinte)? No caso 2,foo
dispararia duas ou uma vez?foo
==bar
, ele seria substituído, caso contrário, ele teria umfoo
e umbar
como ouvintes. Se sempre sobrescrevesse, não seria um conjunto, mas um único objeto que representava um observador.Respostas:
Se você tiver alguns eventos com problemas no gerenciamento de adicionar / remover, eu começaria a adicionar IDs.
Adicionar um ouvinte retorna um ID, a classe que o adicionou controla os IDs dos ouvintes adicionados e, quando precisar removê-los, chama remover ouvinte com esses IDs únicos.
Isso coloca os consumidores no controle, para que possam obter conformidade com o Princípio de menor espanto no comportamento.
Isso significa que adicionar o mesmo duas vezes o adiciona duas vezes, fornece um ID diferente para cada um e a remoção por ID remove apenas o associado a esse ID. Qualquer pessoa que consome a API esperaria esse comportamento ao ver os sigs.
Uma adição adicional em violação ao YAGNI seria um GetIds, onde você entregará um ouvinte e retornará uma lista de IDs associados a esse ouvinte, se ele for capaz de obter verificações de igualdade apropriadas, embora isso dependa do seu idioma: é igualdade de referência? , digite igualdade, igualdade de valor, etc? você precisa ter cuidado aqui, pois pode devolver IDs com os quais esse consumidor não deve remover ou interferir, portanto, essa exposição é perigosa e é desaconselhável e desnecessária, mas GetIDs é um possível complemento se você estiver com sorte.
fonte
IDisposable
,Autocloseable
ou outra interface desse tipo, o objeto de cancelamento de assinatura deve implementar essa interface de maneira segura para threads (sempre possível - se nada mais, colocando o assinante dentro do próprio objeto de cancelamento de inscrição e tendo seu método de cancelamento de inscrição invalidar esse referência e ter solicitações de assinatura ocasionalmente varre a lista de assinaturas em busca de assinaturas inativas).Primeiro, eu escolheria uma abordagem em que a ordem na qual os ouvintes são adicionados é exatamente a ordem em que serão chamados quando os eventos relacionados forem acionados. Se você decidir ir com (1), isso significa que você usa um conjunto ordenado, não apenas um conjunto.
Segundo, você deve esclarecer um objetivo geral de design: sua API deve seguir mais uma estratégia de "falha antecipada" ou uma estratégia de "perdão de erros"? Isso depende do ambiente de uso e dos cenários de uso da sua API. Geralmente, (desenvolvendo principalmente aplicativos de desktop), prefiro "travar mais cedo", mas às vezes é melhor tolerar algum tipo de erro para tornar o uso de uma API mais suave. Os requisitos, por exemplo, em aplicativos incorporados ou aplicativos de servidor podem ser diferentes. Talvez você discuta isso com um de seus possíveis usuários da API?
Para uma estratégia de "falha antecipada", use (2), mas proíba adicionar o mesmo ouvinte duas vezes, lance uma exceção se um ouvinte for adicionado novamente. Também lança uma exceção se alguém tentar remover um ouvinte que não está na lista.
Se você acha que uma estratégia de "perdoar erros" é mais apropriada no seu caso, você pode
ignore a adição dupla do mesmo ouvinte à lista - que é a opção (1) - ou
anexá-lo à lista como em (2), para que ele seja chamado duas vezes quando os eventos forem disparados
ou você o anexa, mas não chame o mesmo ouvinte duas vezes em caso de acionamento de evento
Observe que a remoção do ouvinte deve corresponder a isso - se você ignorar a adição dupla, também deve ignorar a remoção dupla. Se você permitir que o mesmo ouvinte seja adicionado duas vezes, deve ficar claro qual das duas entradas do ouvinte será removida quando uma chamada
removeListener(foo)
. A última das três marcações é provavelmente a abordagem menos suscetível a erros entre essas sugestões; portanto, no caso de uma estratégia de "perdão de erros", eu aceitaria isso.fonte