Events Between Java Components
One of the core concepts of object orientation in Java is that every class can be considered to be a component. Components can be extended or included to form bigger components. The final application is also considered a component. Components are like Lego blocks that build up a bigger structure.
An event in Java is an action that changes the state of a component. For example, if your component is a button, then clicking on that button is an event that changes the state of the button to be clicked.
Events do not necessarily happen only on visual components. For example, you can have an event on a USB component that a device is connected. Or an event on a network component that data is transferred.
Events help to decouple the dependencies between components. Consider the following example.
Assume we have an Oven component and a Person component. They are two components that exist in parallel and work independently of one another. We should not make Person part of Oven, nor the other way around. To build a smart house, we want the Oven to prepare food once Person is hungry. Here are possible implementations:
- Oven checks Person in fixed, short intervals. This not only annoys Person, but is also expensive for the Oven if we want it to check on multiple instances of Person.
- Person comes with a public event Hungry, to which Oven is subscribed. Once Hungry is fired, Oven is notified immediately and starts preparing food.
The second solution uses the event architecture to handle the listening and communication between components efficiently and without a direct coupling between Person and Oven, because Person will fire the event, and any component Oven, Fridge, and Table can listen to that event without any special handling from the Person component.
Implementing events for a Java component can take different formats, depending on how they are expected to be handled, and the logic of the component. Here is an example showing a minimal code to implement a HungerListener in the Person component:
First, create a listener interface:
@FunctionalInterface
public interface HungerListener {
void hungry();
}
Then, in the Person class, define a list to store the listeners:
private List<HungerListener> listeners = new ArrayList<>();
Define an API to insert a new listener:
public void addHungerListener(HungerListener listener) {
listeners.add(listener);
}
You can create a similar API for removing a listener.
Also, add a method to trigger the action of being hungry, that notifies all the listeners about the event:
public void becomesHungry() {
for (HungerListener listener : listeners)
listener.hungry();
}
Finally, from the Oven class, add code that listens to the event and implements the action when the event is fired:
Person person = new Person();
person.addHungerListener(() -> {
System.err.println(“The person is hungry!”);
// Oven takes action here
});
And to try it out:
person.becomesHungry();
For a fully decoupled code, the last section should be in an independent class that has an instance of Person and Oven, and handles the logic between them. Similarly, we can add other actions for Fridge, Table, ..etc. They all will get notified only once the Person becomesHungry.