Ayector de eventos y editor -- java campo con event-handling camp codereview Relacionados El problema

Event Listener and Publisher


11
vote

problema

Español

Este es mi primer intento de crear un sistema de eventos.

Mi plan es que, al publicar, el oyente recibe tanto una referencia al modelo como al editor. Esto permite a la clase del editor tener varios propietarios, y proporcionar una forma de que los oyentes sepan exactamente cómo se llamaron.

Para hacer esto, he hecho las clases de base para el editor / oyente genérico

  abstract class AEventPublisher<Model, Publisher extends AEventPublisher<Model, ?>> {     private List<IEventListener<Model, Publisher>> listeners;      public AEventPublisher(){         listeners = new ArrayList<IEventListener<Model, Publisher>>();     }      public void subscribe(IEventListener<Model, Publisher> listener) {         listeners.add(listener);     }      public void unsubscribe(IEventListener<Model, Publisher> listener) {         listeners.remove(listener);     }      protected void publish(Publisher publisher, Model sender) {         for(IEventListener<Model, Publisher> listener : listeners){             listener.actionPerformed(publisher, sender);         }     }      public abstract void publish(Model sender); }  interface IEventListener<Model, EventPublisher extends AEventPublisher<Model, ?>> {     public void actionPerformed(EventPublisher publisher, Model sender); }   

El método de publicación se hizo protegido y abstracto, porque no pude pensar en una forma de obtener this en AEventPublisher para ser colgado como el Publisher extends AEventPublisher<Model, ?> Tipo


Algunos eventos / oyentes definidos serían entonces:

  public class GameEvents{     public static interface IButtonEventListener extends IEventListener<MenuButton, ButtonEventPublisher>{}      public static class ButtonEventPublisher extends AEventPublisher<MenuButton, ButtonEventPublisher> {         @Override         public void publish(MenuButton sender) {             publish(this, sender);         }     }      public static interface IJoystickEventListener extends IEventListener<Joystick, JoystickEventPublisher> {}      public static class JoystickEventPublisher extends AEventPublisher<Joystick, JoystickEventPublisher> {         @Override         public void publish(Joystick sender) {             publish(this, sender);         }     } }   

Ahora las interfaces de la oyente se pueden convertir en clases anónimas para actuar como devoluciones de llamada.

Este sería un ejemplo de ejemplo como parte del constructor para la clase de mi jugador.

  joystickCallback = new IJoystickEventListener(){     @Override      public void actionPerformed(JoystickEventPublisher event, Joystick sender)      {         updatePlayerDirection(sender);     } }; joystick.getJoystickEventPublisher().subscribe(joystickCallback); //Then before player is deleted, this would be called: //joystick.getJoystickEventPublisher().unsubscribe(joystickCallback);   
Original en ingles

This is my first attempt at creating an event system.

My plan is that upon publishing, the listener receives both a reference to the model and to the publisher. This allows the publisher class to have multiple owners, and provide a way for listeners to know exactly how they were called.

To do this I've made the base classes for the publisher/listener generic

abstract class AEventPublisher<Model, Publisher extends AEventPublisher<Model, ?>> {     private List<IEventListener<Model, Publisher>> listeners;      public AEventPublisher(){         listeners = new ArrayList<IEventListener<Model, Publisher>>();     }      public void subscribe(IEventListener<Model, Publisher> listener) {         listeners.add(listener);     }      public void unsubscribe(IEventListener<Model, Publisher> listener) {         listeners.remove(listener);     }      protected void publish(Publisher publisher, Model sender) {         for(IEventListener<Model, Publisher> listener : listeners){             listener.actionPerformed(publisher, sender);         }     }      public abstract void publish(Model sender); }  interface IEventListener<Model, EventPublisher extends AEventPublisher<Model, ?>> {     public void actionPerformed(EventPublisher publisher, Model sender); } 

The publish method was made protected and abstract, because I couldnt think of a way to get this in AEventPublisher to be casted as the Publisher extends AEventPublisher<Model, ?> type


Some defined event/listeners would then be:

public class GameEvents{     public static interface IButtonEventListener extends IEventListener<MenuButton, ButtonEventPublisher>{}      public static class ButtonEventPublisher extends AEventPublisher<MenuButton, ButtonEventPublisher> {         @Override         public void publish(MenuButton sender) {             publish(this, sender);         }     }      public static interface IJoystickEventListener extends IEventListener<Joystick, JoystickEventPublisher> {}      public static class JoystickEventPublisher extends AEventPublisher<Joystick, JoystickEventPublisher> {         @Override         public void publish(Joystick sender) {             publish(this, sender);         }     } } 

Now the listener Interfaces can be turned into anonymous classes to act as callbacks.

This would be an example usage as part of the constructor for my Player class.

joystickCallback = new IJoystickEventListener(){     @Override      public void actionPerformed(JoystickEventPublisher event, Joystick sender)      {         updatePlayerDirection(sender);     } }; joystick.getJoystickEventPublisher().subscribe(joystickCallback); //Then before player is deleted, this would be called: //joystick.getJoystickEventPublisher().unsubscribe(joystickCallback); 
     

Lista de respuestas

12
 
vote
vote
La mejor respuesta
 

Tengo algunos puntos pequeños sobre este código y luego tengo algunos otros puntos con respecto a su diseño. Intentaré discutir su diseño en perspectiva del desarrollo del juego (donde estoy completamente inexperto) y en perspectiva de las aplicaciones empresariales (más comunes del área de Java).

Nombres y opciones de diseño

Los nombres de su clase (y la interfaz) parecen provenir de una perspectiva de desarrollo dominante de C #. Prefijarse sorted4 a las interfaces Huele a mí .Net a mí. Cada vez que estoy desarrollando Java, trato de mantener las interfaces sin desorden y, por lo general, generalmente sufijo las clases de implementación si se requiere.

Lo siguiente que un poco me mordió fue el prefijo 99887766655443315 para su clase abstracta. No soy un gran amigo de clases abstractas de todos modos. Intento evitarlos donde pueda y preferir las interfaces y "[...] predeterminados" - Implementaciones. Me he estado mejor con eso, ya que las clases abstractas a menudo terminan de restringir el uso de la "interfaz" demasiado.

Prefiero escribir un pequeño código duplicado que crear una clase abstracta central que limite mis posibilidades cuando use la interfaz. En su lugar, crearé una interfaz simple, si no "minimalista". Se pueden combinar múltiples interfaces minimalistas para permitir usos más complejos, pero como su caso de uso es claro ...

De todos modos, esto es suficiente de una charla abstracta sobre las preferencias personales en lo que respecta a las construcciones de lenguaje. Vamos a dar a su código un examen exhaustivo.

Análisis de código:

¿Qué me golpeó instantáneamente cuando vi por primera vez su código fue la falta de modificadores de visibilidad explícitos en su sorted6 y su 99887776655443317 . Ninguno de estos necesita la otra para el contexto tanto que tendría que ponerlos en el mismo archivo de clase.

Lo siguiente que vi fue su especificación increíblemente larga genérica:

  sorted8  

Esto parece ser un gran trabajo innecesario para mí. También está restringiendo innecesariamente las posibilidades que tiene, al usar su mecanismo de publicación. IMO lo siguiente sería suficiente:

  sorted9  

Luego vi cómo almacena count_anagrams(['stop', 'test', 'post']) 0 . Tienes un 99887776655443321

Tienes dos opciones al crear una clase abstracta como esta. O permite el acceso "completo" a sus campos (haciéndolos count_anagrams(['stop', 'test', 'post']) 2 ) o se cierra completamente.

Siguiendo la principio abierto / cerrado (abierto a extensión , cerrado a Modificación ) Definitivamente sugiero que este último. Hiciste eso ni siquiera mal, pero tengo dos pequeños nitpicks para hacer.

  1. Realice sus campos "inmutables" realmente inmutable:

      count_anagrams(['stop', 'test', 'post']) 3  

    es mejor que solo una lista privada.

  2. Hacer uso de características de lenguaje "comunes":
    Actualmente está inicializando su lista en el constructor, y (nuevamente) repita la definición de genéricos clunky. Ahorre de eso y use la función Java 7: la Operador de diamantes . Hace genéricos demasiado clunky (como Java los tiene) más fácilmente por redundancia de achuncia:

      count_anagrams(['stop', 'test', 'post']) 4  

SIGUIENTE ES CÓMO SE HAGA LA PUBLICACIÓN. El problema que está describiendo cuando implementó su método de publicación está enraizado en la forma en que se restringió al comenzar directamente con una clase abstracta, en lugar de tomarse la sala de crear una interfaz para un editor. Considere lo siguiente:

  count_anagrams(['stop', 'test', 'post']) 5  

Esto simplifica enormemente sus declaraciones y anidaciones de genéricos y, además, resuelve el conflicto que tenías internamente.

Si sacrificas una pequeña "flexibilidad" (que probablemente no necesitará de todos modos, más tarde), incluso podría hacer que su Listener

En general, su solución parece sobrecargada por el problema. Lo que me lleva a mi siguiente punto.

Discusión de diseño:

En general, su diseño es algo sobre la parte superior. Intenta mantener la flexibilidad y permitir que las subjubas (en principio sean algo bueno) en lugares donde se complique demasiado su código fuente y perjudicial para la legibilidad de códigos e hiriente a su flexibilidad real.

que viene de una "perspectiva de remejadismo"

Nunca necesitarás un oyente, lo que puede escuchar varias clases de modelos diferentes, y si contra todas las probabilidades usted D, una clase abstracta o una interfaz para agrupar estas clases modelo es la llamada más simple para hacer. Usted fue demasiado lejos en su enfoque idealista para estar "abierto a todo". Esto es algo que debe esforzarse por hacer, pero no al costo de la sencillez y la eficacia.

Esto es fiel a las aplicaciones de GamEdeVelopment y Business.

Además de eso, el remodelamiento de GameEd es un entorno que suele ser muy depende de un rendimiento, y déjame decirle, las interfaces genéricas supercáciladas y las interfaces de retroferencia no son algo para que el JVM se ejecute más rápido.

No tengo idea de por qué lo necesitaría o querer que el oyente obtenga una referencia al editor. Por lo que veo con mi experiencia inexistente, podría "directamente", suscribirse a su modelo, lo que luego pasaría una referencia a su escucha. Pero eso hace que todo el modelo-circo en su código se designa y derrota el punto de un editor separado (que no veo en primer lugar).

En general, la separación del editor de la "publicada" es algo demasiado exagerada y probablemente siga de un error de diseño subyacente. No puedo imaginar una situación en la que tendría que obtener una referencia a un editor, antes de publicar algo. Estás de nuevo: complicando demasiado un asunto simple.

Porque al final, ¿de qué se trata todo esto?

Desea traer datos de un extremo a otro, dependiendo de ciertas condiciones. Y ya hay un lot de las soluciones existentes (comenzando con count_anagrams(['stop', 'test', 'post']) 6 y terminando Dios sabe dónde) en el mundo de Java para eso.

que también es lo que hace que este modelo sea algo problemático desde una perspectiva empresarial. La simplicidad de las soluciones existentes supera su idea fácilmente. El caso de uso práctico es desde mi punto de vista faltante .

En general, reinventó la rueda, pero no como la rueda, sino como un polígono. Su solución para "obtener datos de la A a la D la demanda" sufre de clitud de uso. Es un paseo lleno de baches y no sé cómo lo haría mejor sin aterrizar en algún lugar que ya está pulido para su uso.

Descripción general final:

Cuando revisé su código, lo copié en un proyecto local en mi IDE. Unos pocos cambios y un poco de cabeza después de que yo estuve en el estado del código que sigue.

De cuál es su caso de uso, que debería ser completamente suficiente:

  count_anagrams(['stop', 'test', 'post']) 7  

  count_anagrams(['stop', 'test', 'post']) 8  

  count_anagrams(['stop', 'test', 'post']) 9  

Y ya que también tuviste un ejemplo de una usecase:

  {'opst': ['stop', 'post'], 'estt': ['test']} 0  

Este código debería, en esencia, podrá hacer las mismas cosas que su código puede hacer, pero es mucho más simple. Déjame saber lo que piensas;)

 

I've got a few small points about this code and then I have some other points regarding your design. I'll try to discuss your design in perspective to game-development (where I am completely inexperienced) and in perspective to (the more common Java area) business applications.

Names and Design choices

Your class (and interface) names seem to come from a C# dominated development perspective to me. Prefixing I to interfaces smells .NET to me. Whenever I am developing Java I try to keep interfaces without clutter and then usually suffix the implementation classes if that is required.

Next thing that somewhat bit me was the A prefix for your abstract class. I am not a great friend of abstract classes anyways. I try to avoid them wherever I can and prefer interfaces and "Default[...]"-implementations. I have been going better with that since abstract classes often end up constraining the use of the "Interface" too much.

I'd rather write a little duplicate code than creating a central abstract class that limits my possibilities when using the interface. Instead I'll create a simple, if not "minimalistic" interface. Multiple minimalistic interfaces can be combined to allow more complex uses, but since your use-case is clear ....

Anyways this is enough of abstract talk about personal preferences in regards to language constructs. Let's give your code a thorough examination.

Code Analysis:

What instantly struck me when I first saw your code was the lack of explicit visibility modifiers on your AEventPublisher and your IEventListener. Neither of these needs the other for context so much that you'd have to put them into the same classfile.

The next thing I saw was your incredibly long generics specification:

AEventPublisher<Model, Publisher extends AEventPublisher<Model, ?>> 

This seems to be a lot of unnecessary work to me. Also you're needlessly restricting the possibilities you have, when using your publishing mechanism. IMO the following would be sufficient:

AEventPublisher<Model, P extends Publisher> 

Then I saw how you store listeners. You have a private List<...>

You have two choices when creating an abstract class like this. Either you allow "full" access to your fields (by making them protected) or you close yourself off completely.

Following the Open/Closed principle (Open to extension, Closed to modification) I definitely suggest the latter. You did that not even bad, but I have two small nitpicks to make.

  1. Make your "immutable" fields really immutable:

    private final List<...> 

    is better than just a private list.

  2. Make use of "common" language features:
    You're currently initializing your List in the constructor, and (again) repeat the clunky generics definition. Save yourself from that and use the Java 7 feature: the diamond operator. It makes overly clunky generics (as java has them) easier by shortcircuiting redundancy:

    private final List<IEventListener<Model, Publisher>> listeners = new ArrayList<>(); 

Next up is how you handle publishing. The problem you're describing when you implemented your publish method is rooted in how you restricted yourself when starting directly with an abstract class, instead of taking yourself the room of creating an interface for a Publisher. Consider the following:

public interface Publisher<E> {     public void subscribe(IEventListener<E, Publisher<E>> listener);     public void unsubscribe(IEventListener<E, Publisher<E>> listener);     public void publish(E sender); } 

This greatly simplifies your generics declarations and nestings and additionally resolves the conflict you had internally.

If you'd sacrifice a little "flexibility" (which you problably won't need anyways, more later), you could even make your IEventListener much simpler and completely get rid of the second type-parameter you introduced.

In general your solution seems overengineered for the problem. Which brings me to my next point.

Design Discussion:

In general your design is somewhat over the top. You try to keep flexibility and allow substitions (which is in principle a good thing) in places where it's overly complicating your source-code and detrimental to code-readability and hurtful to your actual flexibility.

Coming from a "GameDevelopment-Perspective"

You will never need a Listener, which can listen to multiple different model classes, and if against all odds you need it, an abstract class or interface to group these model-classes is the simplest call to make. You went too far in your idealistic approach to be "open to everything". This is something you should strive to do, but not at the cost of simplicity and effectiveness.

This is true to both gamedevelopment and business applications.

In addition to that, gamedevelopment is a often heavily performance-reliant environment, and let me tell you, overcomplicated generics and backreferencing interfaces are not something to make the JVM run faster.

I also have no idea why you'd need or want the Listener to obtain a reference to the publisher. From what I see with my nonexistant experience you could "directly" subscribe to your model, which would then pass a reference to itself to it's listener. But that makes the whole Model-circus in your code moot, and defeats the point of a separate Publisher (which I don't see in the first place).

In general, separating the Publisher from the "published" is somewhat overkill and most probably sign of an underlying design mistake. I can not imagine a situation where I'd have to get a reference to a Publisher, before publishing something. You're again: overly complicating a simple matter.

Because in the end, what is this all about?

You want to bring data from one end to another, depending on certain conditions. And there's already a lot of existing solutions (beginning with Observable and ending god knows where) in the Java world for that.

Which is also what makes this model somewhat problematic from a business perspective. The simplicity of existing solutions beats your idea easily. The practical use-case is from my point of view missing.

Overall you reinvented the wheel, but not as wheel, but as a polygon. Your solution to "Get data from A to B on demand" suffers from clunkyness in use. It's a bumpy ride and I don't know how I'd make it better without landing at some place that's already polished for use.

Final overview:

When I reviewed your code I copied it to a local project in my IDE. A few changes and a little headscratching later I was at the state of code that follows.

From what your use-case seems to be, that should be completely sufficient:

public interface Publisher<E> {     public void subscribe(IEventListener<E> listener);     public void unsubscribe(IEventListener<E> listener);     public void publish(E sender); } 

public interface IEventListener<E> {     public void actionPerformed(Publisher<E> publisher, E sender); } 

public class DefaultEventPublisher<Model> implements Publisher<Model> {     private final ArrayList<IEventListener<Model>> listeners = new ArrayList<>();      @Override     public void subscribe(IEventListener<Model> listener) {         listeners.add(listener);     }      @Override     public void unsubscribe(IEventListener<Model> listener) {         listeners.remove(listener);     }      @Override     public void publish(Model sender) {         for (IEventListener listener : listeners) {             listener.actionPerformed(this, sender);         }     } } 

And since you also had an example of a usecase:

public class ButtonEventPublisher         extends DefaultEventPublisher<ButtonEventPublisher.ButtonEvent> {     public static class ButtonEvent {} } 

This code should in essence be able to do the same things that your code can do, but is IMO much simpler. Let me know what you think ;)

 
 
4
 
vote

Vogel612 cubierto de la mayoría de los puntos de estilo, así como el desafío muy apropiado "vale la pena hacerlo".

Pero si vas a hacerlo ...

Una idea importante para comprender por escrito este código bien es que tiene dos inquietudes muy diferentes incrustadas en {'opst': ['stop', 'post'], 'estt': ['test']} 1 . El primero de estos es un editor; También hay un gerente de {'opst': ['stop', 'post'], 'estt': ['test']} 2 . Las cosas serán mucho más claras si exponemos estas abstracciones

  {'opst': ['stop', 'post'], 'estt': ['test']} 3  

Observe que estos genéricos no son restringidos: los conceptos que estamos representando aquí son genéricos, por lo que los tipos deben expresar eso. La complejidad que es específica de este caso de uso se representará en las clases que implementan el caso de uso.

Hablando de qué

  {'opst': ['stop', 'post'], 'estt': ['test']} 4  

Simplificamos el {'opst': ['stop', 'post'], 'estt': ['test']} 5 también.

Como nota lateral, no estoy demasiado interesada en {'opst': ['stop', 'post'], 'estt': ['test']} 6 . "Acción realizada" es un gran nombre (excusado de la vaguedad) para un evento, pero está mal para un método de objeto. Preferiría fuertemente {'opst': ['stop', 'post'], 'estt': ['test']} 7 - Para un controlador genérico - o {'opst': ['stop', 'post'], 'estt': ['test']} 88 . El hecho de que estamos anticipando a los genéricos hacen una buena nombres más difícil; Pero creo que {'opst': ['stop', 'post'], 'estt': ['test']} 9 sería más consistente con las mejores prácticas.

En este punto, tenemos las interfaces que necesitamos para hacer algo aterrador ...

  len0  

Descargo de responsabilidad: el formato loco para la legibilidad es un olor a código: una buena revisión de código debe desafiarlo duro sobre si esto introduce complejidad innecesaria.

Entonces, ¿qué diablos es esto? Rompiéndolo, estamos definiendo una clase con dos parámetros genéricos. E es un evento, que puede ser cualquier cosa; P es ... Bueno, es cualquier cosa que extienda el len1 Estamos definiendo actualmente. La clase también promete implementar el agradable limpio len2 y len3 Interfaces que definimos anteriormente.

A través de los genéricos, podemos resolver la dependencia circular; Todo es azúcar sintáctico, por lo que aunque nos asombramos hasta el final, los tipos se borrarán cuando el compilador va a trabajar.

¿Por qué estamos haciendo esto loco?

El método de publicación se hizo protegido y abstracto, porque no pude pensar en una forma de hacer que esto en AEVENTPublisher sea unido que el editor se extiende aEventPublisher

Derecha: simplemente no podemos usar len4 en la clase base y preservar el comportamiento especializado que queremos. Batimos con el parámetro aparentemente autosuficientemente 99887766555443345 en la firma: nuestro 998877665555443346 va a proporcionar una implementación de len7 con la firma exacta Necesitamos que tenga para el len8 hacer lo correcto.

Probemos un ejemplo: quería que mi IDE me mostrara que las cosas realmente funcionaron, por lo que primero creé algunas implementaciones que se utilizarán en la especialización de la plantilla.

  len9  

Eso es suficiente para probar que el def convert_output(words_in_anagram_class): return {word: len(words) for words in words_in_anagram_class.values() for word in words} 0 está realmente manejando el tipo de evento correcto. Pero también quiero ver que realmente puede distinguir el tipo correcto de def convert_output(words_in_anagram_class): return {word: len(words) for words in words_in_anagram_class.values() for word in words} 1 , por lo que agrego una interfaz adicional que se espera que el propio código se implemente.

  def convert_output(words_in_anagram_class):     return {word: len(words)             for words in words_in_anagram_class.values()              for word in words} 3  

Claramente, estoy corriendo bajo la creatividad aquí.

OK, ShowTime - ¿Qué aspecto tiene el def convert_output(words_in_anagram_class): return {word: len(words) for words in words_in_anagram_class.values() for word in words} 4

  def convert_output(words_in_anagram_class):     return {word: len(words)             for words in words_in_anagram_class.values()              for word in words} 5  

def convert_output(words_in_anagram_class): return {word: len(words) for words in words_in_anagram_class.values() for word in words} 6 Amplifica def convert_output(words_in_anagram_class): return {word: len(words) for words in words_in_anagram_class.values() for word in words} 7 ¿Qué significa que realmente es un P que se extiende 99887776655443358 y el compilador está satisfecho!

A def convert_output(words_in_anagram_class): return {word: len(words) for words in words_in_anagram_class.values() for word in words} 9 que se suscribe al convert_output(count_anagrams(words))0 Realmente obtiene métodos que no son parte del convert_output(count_anagrams(words))1 Interfaz:

  convert_output(count_anagrams(words))2  

y tanto la suscripción como la publicación "solo funcionan".

  convert_output(count_anagrams(words))3  

un par de puntos adicionales. El patrón "Self abstracto ()" aparece en otros contextos, es muy útil para implementar constructores fluidos. Para que pueda considerar bromear esa pieza.

  convert_output(count_anagrams(words))4  

que es agradable, limpio, reutilizable ... funciona bien cuando está construyendo una nueva clase desde cero. Sin embargo, si está tratando de extender alguna otra clase, el idioma no le permitirá extender una segunda clase, y tendrá que volver a escribir el 998877776655443365 Declaración de todos modos. Puede que no valga la pena el esfuerzo.

segundo: es posible que no desee restringir los tipos de convert_output(count_anagrams(words))6 se adjuntan a convert_output(count_anagrams(words))7 . Por ejemplo, es posible que tenga un oyente genérico que quiera suscribirse a eventos de joystick y eventos de teclado. Podemos apoyar eso aquí aflojando las restricciones a los oyentes. Eso va a cambiar el convert_output(count_anagrams(words))8 de la interfaz al dejar todo lo demás, otra sugerencia que observar y publicar es dos inquietudes separadas.

  convert_output(count_anagrams(words))9  

El compilador es lo suficientemente feliz ...

  map0  

Finalmente, puede encontrar que desea extender map1 . En la implementación anterior, la clase fue declarada deliberadamente definida para cerrar esa puerta, porque la definición de map2 proporcionado por 99887766555443373 va a evitar que los oyentes de eventos accedan a los nuevos métodos en map4 . La respuesta correcta es refactar el map5 en dos piezas: una pieza abstracta y extensible que tiene todas las partes interesantes de la interfaz, y una pieza final de concreto que implementa map6

  map7  

Esta implementación nos permite crear un tipo de map8 que comparte la implementación de la interfaz 99887766555443379 además de proporcionar nuevos métodos ...

  [f(x) for x in iterable]0  

 

Vogel612 covered most of the style points, as well as the very appropriate challenge "is this worth doing".

But if you are going to do it....

An important idea to understand in writing this code well is that you have two very different concerns embedded in AEventPublisher. The first of these is a Publisher; there is also a manager of EventListeners. Things will be much clearer if we expose these abstractions

public interface Publisher<E> {     public void publish(E sender); }  public interface Observable<L> {     void subscribe(L listener);     void unsubscribe(L listener); } 

Notice that these generics are unconstrained - the concepts we are representing here are generic, so the types should express that. The complexity that is specific to this use case will be represented in the classes that implement the use case.

Speaking of which

interface EventListener<E, P> {     void actionPerformed(P publisher, E event); } 

We simplify the EventListener as well.

As a side note, I'm not too keen on actionPerformed. "Action Performed" is a great (excusing the vagueness) name for an event, but it's wrong for an object method. I'd strongly prefer either onEvent -- for a generic handler -- or onEventHappened. The fact that we're anticipating generics does make good naming harder; but I think onActionPerformed would be more consistent with best practices.

At this point, we have the interfaces that we need to do something scary....

abstract static class EventPublisher         < E         , P extends EventPublisher<E,P>>         implements         Publisher<E>         , Observable<EventListener<E,P>> {     List<EventListener<E,P>> listeners;      public void subscribe(EventListener<E,P> listener) {         listeners.add(listener);     }      public void unsubscribe(EventListener<E,P> listener) {         listeners.remove(listener);     }      @Override     public void publish(E event) {         for(EventListener<E,P> listener : listeners){             listener.actionPerformed(self(),event);         }     }      // NOTE: this should only be implement in final classes!     abstract protected P self();  } 

Disclaimer: crazy formatting for readability is a code smell -- a good code review should challenge you hard on whether this introduces unnecessary complexity.

So what the heck is this? Breaking it down, we're defining a class with two generic parameters. E is an event, which can be anything; P is... well, it's anything that extends the EventPublisher we are currently defining. The class also promises to implement the nice clean Publisher and Observable interfaces we defined earlier.

Through the generics, we're able to resolve the circular dependency; it's all syntactic sugar, so although we get turtled all the way down, the types are all going to be erased when the compiler goes to work.

Why are we doing this crazy thing?

The publish method was made protected and abstract, because I couldnt think of a way to get this in AEventPublisher to be casted as the Publisher extends AEventPublisher

Right -- we simply cannot use this in the base class and preserve the specialized behavior that we want. We beat with the seemingly self referential P parameter in the signature -- our specialized EventPublisher is going to provide an implementation of self() with the exact signature we need it to have for the EventListeners to do the right thing.

Let's try an example - I wanted my IDE to show me that things actually worked, so first I created some implementations to be used in the template specialization.

interface Joystick {     int getDirection(); }  static class Game {     static void updatePlayerDirection(Joystick joystick) {         joystick.getDirection();     } } 

That's enough to prove that the EventHandler is really handling the right type of event. But I also want to see that it can really distinguish the right kind of EventPublisher, so I add an extra interface that the JoystickEventPublisher itself is expected to implement.

interface BobNotifier {     void notifyBob(); } 

Clearly, I'm running low on creativity here.

OK, showtime - what does the JoystickEventPublisher look like?

final static class JoystickEventPublisher extends EventPublisher<Joystick, JoystickEventPublisher>         implements BobNotifier {      @Override     public void notifyBob() {         //...     }      // We're "allowed" to implement self(), because this class is final.     protected JoystickEventPublisher self() {         return this;     } } 

JoystickEventPublisher extends EventPublisher<E,JoystickEventPublisher<E,P>> which means it really is a P that extends EventPublisher<E,P> and the compiler is satisfied!

A JoystickEventListener which subscribes to the JoystickEventPublisher really gets to use methods that are not part of the EventPublisher interface:

static class JoystickEventListener implements EventListener<Joystick, JoystickEventPublisher> {      public void actionPerformed(JoystickEventPublisher publisher, Joystick joystick)     {         publisher.notifyBob();         Game.updatePlayerDirection(joystick);     } }; 

And both subscription and publication "just work".

    JoystickEventPublisher p = new JoystickEventPublisher();     p.subscribe(new JoystickEventListener());     p.publish(new Joystick() {         @Override         public int getDirection() {             return 0;         }     }); 

A couple of further points. The "abstract self()" pattern shows up in other contexts -- it's very useful for implementing fluent builders. So you might consider teasing that piece out.

abstract static class AbstractSelf<T extends AbstractSelf<T>> {     // NOTE: this should only be implement in final classes!     abstract protected T self(); }  abstract static class EventPublisher         < E         , P extends EventPublisher<E,P>>         extends          AbstractSelf<P>         implements         Publisher<E>         , Observable<EventListener<E,P>> {         //... } 

Which is nice, clean, re-usable.... It works great when you are building a new class from scratch. However, if you are trying to extend some other class, the language won't allow you to extend a second class, and you'll have to re-write the self() declaration anyway. It may not be worth the effort.

Second - you may not want to restrict the kinds of Listeners are attached to EventPublishers. For instance, you might have a generic listener that wants to subscribe to joystick events and keyboard events. We can support that here by loosening the constraints on the listeners. That's going to change the Observer part of the interface while leaving everything else alone -- another hint that observe and publish are two separate concerns.

abstract static class EventPublisher         < E         , P extends EventPublisher<E,P>>         implements         Publisher<E>         , Observable<EventListener<? super E, ? super P>> {     List<EventListener<? super E, ? super P>> listeners;      public void subscribe(EventListener<? super E, ? super P> listener) {         listeners.add(listener);     }      public void unsubscribe(EventListener<? super E, ? super P> listener) {         listeners.remove(listener);     }      @Override     public void publish(E event) {         for(EventListener<? super E, ? super P> listener : listeners){             listener.actionPerformed(self(),event);         }     }      // NOTE: this should only be implement in final classes!     abstract protected P self();  } 

The compiler is happy enough....

    p.subscribe(new EventListener<Object, Object>() {         @Override         public void actionPerformed(Object publisher, Object event) {             //...         }     }); 

Finally, you may find that you want to extend JoystickEventPublisher. In the implementation above, the class was deliberately declared final to close that door, because the definition of self() provided by JoystickEventPublisher is going to prevent the event listeners from accessing any new methods in ExtendedJoystickEventPublisher. The right answer is to refactor the JoystickEventPublisher into two pieces - an abstract and extendable piece that has all of the interesting parts of the interface, and a final concrete piece that implements self()

abstract static class AbstractJoystickEventPublisher<P extends AbstractJoystickEventPublisher<P>> extends EventPublisher<Joystick, P>         implements BobNotifier {      @Override     public void notifyBob() {         //...     }  }  final static class JoystickEventPublisher extends AbstractJoystickEventPublisher<JoystickEventPublisher> {     // We're "allowed" to implement self(), because this class is final.     protected JoystickEventPublisher self() {         return this;     } } 

This implementation allows us to create a kind of JoystickEventPublisher which shares the implementation of the BobNotifier interface in addition to providing new methods....

abstract static class AbstractEnhancedJoystickEventPublisher<P extends AbstractEnhancedJoystickEventPublisher<P>> extends AbstractJoystickEventPublisher<P> {     public void notifyAlice() {         // ...         }     }  final static class EnhancedJoystickEventPublisher extends AbstractEnhancedJoystickEventPublisher<EnhancedJoystickEventPublisher> {      protected EnhancedJoystickEventPublisher self() {         return this;     } } 
 
 
   
   

Relacionados problema

1  Manipulador de eventos repetitivos para un control de interfaz de usuario de la UI  ( Repetitive event handler for a toggling ui control ) 
Siento que este tipo de código podría haber sido escrito más elegante, especialmente con las enormes afirmaciones de IF / ODS. ¿Alguien puede ayudarme a rompe...

4  Inicializando eventos en clase  ( Initializing events on class ) 
Escribí una clase simple que sustituye todas las entradas de archivos en un HTML para una plantilla más compleja y configura algunas acciones en él. El código...

13  Configuración del botón de radio, haga clic en ActionListeners  ( Setting up radio button click actionlisteners ) 
En lugar de escribir cuatro llamadas de método, me opté por iterar sobre una matriz, pero me pregunto si esta era la mejor manera de hacerlo. ..escribí a Ithe...

7  Incluso manejador para la vista previa de entrada de texto  ( Even handler for text input preview ) 
En una aplicación WPF, tengo el siguiente controlador de eventos para (x1,y1),(x2,y2)8 en (x1,y1),(x2,y2)9 Control. El propósito principal de este Código ...

5  Mouselistener Lag en el juego de azulejos de piano  ( Mouselistener lag in piano tiles game ) 
Estaba intentando escribir mi propia versión simple de la aplicación de juegos de piano azulejos en Java, así como un ejercicio divertido. gamelogic.java ...

6  ¿Está mi delegado definió la forma correcta y necesita transformarse en una mariposa bonita?  ( Is my delegate defined the right way and does it need to transform to a pretty e ) 
He leído tantos Historias de tiempo de cama e inserciones de cómo < fuertes> delegados trabajo y por qué eventos deben ser reemplazados por los delegado...

7  Control de finanzas con desarrollo web  ( Controlling finances with web development ) 
Propósito Primero, déjame explicar el título: Anteriormente, había manejado mis finanzas como una compañía antigua, y funcionaba maravillas por tomar decisi...

6  Sistema de eventos personalizado en C #  ( Custom event system in c ) 
Actualmente estoy escribiendo un juego y quiero codificar todo lo que sucede (GameObject se ha movido, HP cambió, fue creado / destruido, se ha creado / perdi...

6  Vuelos divectores de división vue.js  ( Resizable split divs vue js ) 
Acabo de empezar a desarrollar la aplicación web (VUE.JS) para mi empresa por alrededor de 1-2 meses. Por lo tanto, mi conocimiento y experiencia en HTML, CSS...

8  Grabadora para eventos de teclado y ratón  ( Recorder for keyboard and mouse events ) 
Estoy construyendo una grabadora de eventos. Hasta ahora, el código se ve limpio, pero siento que me falta algo que puede hacer que este código sea aún mejor....




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos