Continuous Event Processing

I have implemented an immutable system in Java. Almost every class is immutable, and it works much better than I expected.

My problem is trying to send events. Usually you have an event source and an event listener. The source simply refers to the listener and dispatches the event when it occurs.

But with an immutable link, the link to the event listener changes when the field changes and a new object is created. Thus, the source of the event sends to some old link that was collected by garbage.

Thus, all my GUI classes change for this reason, because they naturally use a lot of events. But I would like to find an elegant way to handle events so that I can make them immutable.

Edit: sample code on request:

public final class ImmutableButton { public final String text; public ImmutableButton(String text) { this.text = text; } protected void onClick() { // notify listeners somehow, hoping they haven't changed } } public final class ImmutableWindow { public final ImmutableButton button; public ImmutableWindow(ImmutableButton button) { this.button = button; } protected void listenForButtonClick() { // somehow register with button and receive events, despite this object // being entirely recreated whenever a field changes } } 
+5
source share
2 answers

Graphical interfaces are a good example where variability is more convenient and more efficient to use as an object model. You create one support model for the GUI component with fields ready for read and write operations. The user mutates the display, which is reflected in the swap model - everything happens in one object. Imagine that you need to recreate an object every time a user changes something on one component? This will surely put you in order.

Despite the drawback, if you really want your GUI objects to be immutable, you can create a global event bus to solve the problem of attaching a listener. This way you do not need to worry about the instance of the object that the event listener will listen on. The event bus will be responsible for dispatching events, registering the listener, and maintaining consistency between them.

Here is a design for a simple event bus.

 public class EventBus { private Map<Event, List<EventListener>> REGISTRY; public void registerEventListener(Event event, EventListener listener) { List<EventListener> listeners = REGISTRY.getOrDefault(event, new ArrayList<>()); listeners.add(listener); } public void fireEvent(Event event, Object... args) { List<EventListener> listeners = REGISTRY.get(event); if(listeners != null) { for(EventListener listener : listeners) { listener.handleEvent(args); } } } } // The events enum Event { ADD_BUTTON_CLICKED, DELETE_BUTTON_CLICKED; } // Listeners must conform to one interface interface EventListener { public void handleEvent(Object... args); } 

EDIT

Listeners are handlers — they must execute business logic and not preserve state. In addition, they should not be attached to the component. In the code above, the listener code must be separate from ImmutableWindow - both must stand alone. The interaction between ImmutableWindow and ImmutableButton must be configured (in the event bus) somewhere during the launch of your application.

You should also have a central registry of your user interface components, where it can be identified by a unique identifier and use this registry to search (cross the component tree) for the last instance of the component and interact with it.

In practice, something like this ...

 // The main class. Do the wirings here. public class App { @Inject private EventBus eventBus; @PostConstruct public void init() { ImmutableWindow window = new ImmutableWindow (); ImmutableButton addButton = new ImmutableButton (); eventBus.registerEventListener(Events.ADD_BUTTON_CLICKED, new AddButtonClickListener()); } } public class AddButtonClickListener implements EventListener { @Inject private SomeOtherService someOtherSvc; @Inject private UiRegistry uiRegistry; public void handleEvent(Object... args) { ImmutableButton addButton = args[0].getSource; // The newset instance of the button must be packed and delivered to the eventlistners when firing an event ImmutableWindow targetWindow = uiRegistry.lookUp("identifier_of_the_window", ImmutableWindow.class); // Perform interaction between the two components; } } 

You now have a completely decoupled interface and business logic. You can recreate your components, whatever you want, the listeners will not be affected, because they are not tied to any component.

+3
source

Yes, the conflict in your design is that you cannot listen to listening records regarding objects that are replaced when restoring an immutable data model. My solution below is to remove listeners from the model all together. Even if most of your objects are immutable, you will need at least one mutable variable in the database to save the created / recreated window. Here I used inner classes as listeners that you register once. They then make doStuff () calls against objects in the model, but they turn against the only basic mutable window reference. eg. window.getButton1().doStuff();

I am not saying that this is a great solution, but it is the simplest and cleanest I could come up with for your requirements. Listeners do not become invalid, and everything from the window down can be unchanged.

 public final MutableBase { private ImmutableWindow window; // single mutable variable public MutableBase() { Magic.registerClickListener(new WindowClickEvent()); Magic.registerClickListener(new Button1ClickEvent()); rebuildWindow(); } public void rebuildWindow() { // rebuild here when needed - change single mutable variable this.window = new ImmutableWindow(new ImmutableButton("text")); } class WindowClickEvent implements ClickListener { public void onClick() { this.window.doStuff(); } } class Button1ClickEvent implements ClickListener { public void onClick() { this.window.getButton1().doStuff(); } } } 
0
source

Source: https://habr.com/ru/post/1273126/


All Articles