Al diseñar una API que proporciona una interfaz de escucha de eventos, parece que hay dos formas conflictivas de tratar las llamadas para agregar / quitar escuchas:
Múltiples llamadas a addListener solo agregarán un único oyente (como agregarlo a un conjunto); se puede eliminar con una sola llamada a removeListener.
Múltiples llamadas a addListener agregarán un oyente cada vez (como agregarlo a una lista); debe estar equilibrado por varias llamadas para eliminarListener.
He encontrado un ejemplo de cada uno: (1) - La llamada DOM addEventListener en los navegadores solo agrega oyentes una vez, ignorando silenciosamente las solicitudes para agregar el mismo oyente por segunda vez y (2) - El .on
comportamiento de jQuery agrega oyentes varias veces .
La mayoría de las otras API de escucha parecen usar (2), como SWT y oyentes de eventos Swing. Si se elige (1), también está la cuestión de si debería fallar silenciosamente o con un error cuando hay una solicitud para agregar el mismo oyente dos veces.
En mis implementaciones, tiendo a seguir con (2) ya que proporciona una interfaz de tipo de configuración / desmontaje más limpia y revela errores en los que la 'configuración' se realiza involuntariamente dos veces, y es consistente con la mayoría de las implementaciones que he visto.
Esto me lleva a mi pregunta: ¿hay una arquitectura particular u otro diseño subyacente que se preste mejor a la otra implementación? (es decir, ¿por qué existe el otro patrón?)
fuente
addListener(foo); addListener(foo); addListener(bar);
. ¿Su caso # 1 agrega unofoo
y unobar
, o solobar
(es decir, sebar
sobrescribefoo
como el oyente)? En el caso # 2, ¿sefoo
dispararía dos veces o una vez?foo
==bar
, entonces se sobrescribiría, de lo contrario, tendría unofoo
y unobar
como oyentes. Si siempre sobrescribe, no sería un conjunto, sino un solo objeto que representara un observador.Respuestas:
Si tiene algunos eventos con los que tiene problemas para administrar las adiciones / eliminaciones, comenzaría a agregar ID.
Agregar un escucha devuelve un ID, la clase que lo agregó realiza un seguimiento de los ID de los oyentes que se agrega y cuando necesita eliminarlos, llama a eliminar escucha con ese / esos ID (s) únicos.
Esto pone a los consumidores en control para que puedan cumplir con el Principio de Menos Asombro en el comportamiento.
Esto significa que agregar el mismo dos veces lo agrega dos veces, proporciona una ID diferente para cada una, y la eliminación por ID elimina solo la asociada con esa ID. Cualquiera que consuma la API esperaría este comportamiento cuando viera las señales.
Una adición adicional en violación de YAGNI sería un GetIds donde le entregue un oyente y devuelva una lista de ID asociados con ese oyente si es capaz de obtener las verificaciones de igualdad adecuadas, aunque eso depende de su idioma: ¿es igualdad de referencia? , tipo igualdad, valor igualdad, etc. debe tener cuidado aquí porque puede devolver identificaciones que ese consumidor no debería eliminar o entrometerse, por lo tanto, esta exposición es peligrosa y no es aconsejable y debe ser innecesaria, pero GetID es una posible adición si se siente afortunado.
fuente
IDisposable
,Autocloseable
o en otra interfaz, el objeto de anulación de suscripción debe implementar la interfaz en la moda thread-safe (siempre es posible - si nada más colocando el abonado dentro de la propia objeto de darse de baja, y que tiene su método de cancelación de suscripción invalidate que referencia, y tener solicitudes de suscripción ocasionalmente escanea la lista de suscripciones en busca de suscripciones muertas).Primero, elegiría un enfoque donde el orden en que se agregan los oyentes es exactamente el orden en que se los llamará cuando se activen los eventos relacionados. Si decide ir con (1), eso significará que usa un conjunto ordenado, no solo un conjunto.
En segundo lugar, debe aclarar un objetivo de diseño general: ¿su API seguirá más una estrategia de "bloqueo anticipado" o una estrategia de "perdón de errores"? Esto depende del entorno de uso y los escenarios de uso de su API. En general, (desarrollando principalmente aplicaciones de escritorio) prefiero "bloquear antes", pero a veces es mejor tolerar algún tipo de error para hacer que el uso de una API sea más fluido. Los requisitos, por ejemplo, en aplicaciones integradas o aplicaciones de servidor pueden ser diferentes. ¿Quizás hable de esto con uno de sus usuarios potenciales de API?
Para una estrategia de "bloqueo anticipado", use (2), pero prohíba agregar el mismo oyente dos veces, arroje una excepción si se agrega un oyente nuevamente. También arroje una excepción si intenta eliminar un oyente que no está en la lista.
Si cree que una estrategia de "perdón de errores" es más apropiada en su caso, podría
ignore la doble adición del mismo oyente a la lista, que es la opción (1), o
añádalo a la lista como en (2), por lo que se llamará dos veces cuando se activen los eventos
o lo agrega, pero no llame al mismo oyente dos veces en caso de activación de eventos
Tenga en cuenta que la eliminación del oyente debe corresponder a eso: si ignora la doble adición, también debe ignorar la doble eliminación. Si permite que se agregue el mismo oyente dos veces, debe quedar claro cuál de las dos entradas del oyente se eliminará cuando una llame
removeListener(foo)
. La última de las tres viñetas es probablemente el enfoque menos propenso a errores entre esas sugerencias, por lo que en caso de una estrategia de "perdón de errores", iría con eso.fuente