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.
Assume we have an Oven
component and a Person
component. These two components 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 two possible implementations:
Oven
checks Person
in fixed, short intervals. This annoys Person
and is also expensive for 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 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, such as 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 forms, depending on how they are expected to be handled. To implement a minimal 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 to notify all listeners of 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 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
, and so on. They all will get notified only once the Person becomesHungry
.