Constructor Value Vs. Observer in Java
Learn more about the constructor value and Observer in Java.
Join the DZone community and get the full member experience.
Join For FreeWho Belongs to Whom?
It is common to connect two components using constructor parameters. This procedure can be seen very clearly, for example, in the construction of graphic surfaces. Take the following source code:
public class SubView {
private MainView mainView;
public SubView(MainView mainView) {
this.mainView = mainView;
}
public void buttonClicked(String input) {
mainView.setInputValue(input);
}
}
This subcomponent gets the surrounding main component via the constructor. The instance of this given component is just needed for a value transit. The value is the result of a user interaction inside the subcomponent and consumed inside the surrounding element.
This procedure leads to results in various challenges that I find avoidable. First, this sub-component is hard-bound to the main element. This hard-coupled type makes no sense since this bond is based purely on the use of the generated values.
Furthermore, the testing of the sub-component is more difficult because an instance of the main component or a corresponding MOCK must be used. Again, this is an additional requirement that merely adds complexity and at the same time, reduces the abstraction of the individual components. So what can you do here and add that to the project without another framework as a dependency?
Observer: The Classic
There is a straightforward design pattern that can help here. We're talking about the Observer pattern.
public class Observable<KEY, VALUE> {
private final Map<KEY, Consumer<VALUE>> listeners = new ConcurrentHashMap<>();
public void register(KEY key, Consumer<VALUE> listener) {
listeners.put(key, listener);
}
public void unregister(KEY key) {
listeners.remove(key);
}
public void sentEvent(VALUE event) {
listeners.values()
.forEach(listener -> listener.accept(event));
}
}
The basic principle consists of three interactions. In a map, a consumer is stored for a given key. The consumer is the utilization unit for an event or better an input type that is defined by the second type definition of the map. You can register with a key and later the associated consumer can also be removed again from this map if needed.
If a data package is now ready for processing, it will be sent to all consumers who have already registered at this time using the method for posting events. In practice, all existing consumers in the map are processing the value once in an undefined order. The sent event must be immutable itself.
Registry
To simplify the process of registering and unsubscribing, you can modify the Observer design pattern a bit.
public interface Registration {
void remove();
}
public class Registry<KEY, VALUE> {
private final Map<KEY, Consumer<VALUE>> listeners = new ConcurrentHashMap<>();
public static <K, V> Registry<K, V> instance() {
return new Registry<>();
}
public Registration register(KEY key, Consumer<VALUE> listener) {
listeners.put(key, listener);
return () -> listeners.remove(key);
}
public void sentEvent(VALUE event) {
listeners.values()
.forEach(listener -> listener.accept(event));
}
}
For this, a functional interface with the name Registration
is defined, which only provides the method to remove itself from the registry. This method implements the respective logout process. The instance of a Registration
is the return value of a registration process itself.
The processing of the event data is made in the same way we have done it at the implementation of the Observable. The practical use is shown below in a JUnit5 test:
final Registry<String, Event> eventBus = Registry.instance();
final String expected = "message 001";
final AtomicInteger counter = new AtomicInteger(0);
final String key01 = "Consumer-01";
final String key02 = "Consumer-02";
final Registration register01 = eventBus.register(key01, (event) -> {
assertEquals(expected, event.getMessage());
counter.incrementAndGet();
});
final Registration register02 = eventBus.register(key02, (event) -> {
assertEquals(expected, event.getMessage());
counter.incrementAndGet();
});
eventBus.sentEvent(new Event(expected, ""));
Assertions.assertEquals(2, counter.get());
register01.remove();
eventBus.sentEvent(new Event(expected, ""));
Assertions.assertEquals(3, counter.get());
Coupling of Components
Let's start with the sub-component. This element is no longer linked to the main component. In this example, I am using a general static event bus of type Registry
. Of course, you can also use your event bus instance per component, which further decouples the component.
public class SubView {
public void buttonClicked(String input) {
EVENT_BUS.sentEvent(new Event(input));
}
public Registration register(String key, Consumer<Event> listener) {
return EVENT_BUS.register(key, listener);
}
}
If another component wants to use the values of the fictitious user interaction, it can register with the instance of the sub-component.
public static class MainView {
//for demo public
public SubView subView = new SubView();
private Registration registration = subView.register("keyXYZ",
e -> inputValue = e.getValue());
private String inputValue;
public String getInputValue() {
return inputValue;
}
public void release() {
registration.remove();
}
}
The corresponding JUnit5 test looks like this.
final MainView mainView = new MainView();
final String inputValue = "inputValue";
//subview is public for demo
mainView.subView.buttonClicked(inputValue);
Assertions.assertEquals(inputValue, mainView.getInputValue());
Conclusion
With a few lines of source code, we have not only decoupled the components much better from each other but also simplified the testing of the individual elements. There are no mocks needed anymore. The increased abstraction also allows more than one component to register on the sub-component shown here. Of course, one should not forget at this point that the log-off from a registry should not be forgotten to allow the garbage collector to function correctly.
The source code for these source code examples can be found here.
Happy coding!
Further Reading
The Observer Pattern Using Java 8
Opinions expressed by DZone contributors are their own.
Trending
-
Why You Should Consider Using React Router V6: An Overview of Changes
-
What Is React? A Complete Guide
-
Writing a Vector Database in a Week in Rust
-
Authorization: Get It Done Right, Get It Done Early
Comments