I was thinking of adding a very high-level implementation of the event/observer pattern to my project. The implementation would manage adding and removing observers as "needed". You can use an almost DLS-like API to define when an observer should be added or removed.
Here is an example of the API in use. We want to track mouse dragging along a path:
public void registerMousePathTracker(
Mouse mouse,
Consumer<List<Point>> callback
)
{
final List<Point> points = new ArrayList<>();
observe(mouse)
.when(MouseDown.class)
.Do(evt -> {
points.clear();
points.add(evt.getPoint());
})
.once()
.laterWhen(MouseMove.class)
.Do(evt -> {
points.add(evt.getPoint());
})
.until(MouseUp.class)
.thenDo(evt -> {
points.add(evt.getPoint());
callback.accept(points);
})
.once()
.repeatIndefinitely()
;
}
This code defines 3 observers:
- when the mouse is pressed down we clear the points list and add the point where the mouse was pressed. Then we remove this observer from mouse and add the next observer.
- every time the mouse is being moved we add a point to the points list.
- when the mouse is released (no longer pressed) we remove the second observer and add the third. The third observer adds the current mouse position to the points list and calls the callback that was passed as a parameter. Afterwards the third observer removes itself from the mouse.
- After the third observer was removed the first observer is added again to create a full circle.
The observe-Method is defined by the ObserverManager interface:
public interface ObserverManager {
public DefineEventType observe(EventSrc eventSource);
@SuppressWarnings("unchecked")
public default <X extends Event> Consumer<X> nothing() {
return (Consumer<X>) DO_NOTHING;
}
public static final Consumer<Event> DO_NOTHING = e -> {};
public static interface DefineEventType {
public <X extends Event> DefineAction<X> when(Class<X> eventType);
}
public static interface DefineAction<X extends Event> {
public DefineCondition<X> Do(Consumer<X> action);
}
public static interface DefineCondition<X extends Event> {
public DefineChainedEvent once();
public void always();
public DefineChainedEvent While(Predicate<X> continueCondition);
public DefineChainedEvent until(Predicate<X> stopCondition);
public <Y extends Event> DefineFollowUpAction<Y> until(Class<Y> stopEventType);
}
public static interface DefineChainedEvent {
public <X extends Event> DefineAction<X> laterWhen(Class<X> event);
public void repeatIndefinitely();
}
public static interface DefineFollowUpAction<X extends Event> extends DefineChainedEvent {
public DefineCondition<X> thenDo(Consumer<X> action);
}
}
There is one implementation of this interface which could either be used as a delegate or as a super-class:
public class ObserverManagerImpl implements ObserverManager {
@Override
public DefineEventType observe(EventSrc eventSource) {
return new ObserveBuilder(this, eventSource);
}
@SuppressWarnings("rawtypes")
static class ObserveBuilder implements DefineEventType, DefineChainedEvent, DefineAction, DefineFollowUpAction, DefineCondition {
private final ManagedObserver first;
private ManagedObserver latest;
private Class mostRecentStopEvent;
ObserveBuilder(ObserverManager source, EventSrc target) {
first = new ManagedObserver(source, target);
latest = first;
}
private void registerIfNeeded() {
if (latest == first) {
first.register();
}
}
@SuppressWarnings("unchecked") @Override
public <X extends Event> DefineAction<X> when(Class<X> event) {
latest.evtType = event;
return this;
}
@SuppressWarnings("unchecked") @Override
public <X extends Event> DefineAction<X> laterWhen(Class<X> event) {
ManagedObserver prev = latest;
latest = new ManagedObserver(prev.source, prev.target);
latest.evtType = event;
prev.followUp = latest;
return this;
}
@SuppressWarnings("unchecked") @Override
public DefineCondition Do(Consumer action) {
latest.action = action;
return this;
}
@SuppressWarnings("unchecked") @Override
public DefineCondition thenDo(Consumer action) {
ManagedObserver prev = latest;
latest = new ManagedObserver(prev.source, prev.target);
latest.evtType = mostRecentStopEvent;
prev.followUp = latest;
return Do(action);
}
@Override
public DefineChainedEvent once() {
Class<?> evtType = latest.evtType;
latest.unregisterCondition = evt -> evtType.isInstance(evt);
registerIfNeeded();
return this;
}
@Override
public void always() {
latest.unregisterCondition = null;
registerIfNeeded();
}
@Override
public DefineFollowUpAction until(Class stopEvent) {
mostRecentStopEvent = stopEvent;
latest.unregisterCondition = evt -> stopEvent.isInstance(evt);
registerIfNeeded();
return this;
}
@SuppressWarnings("unchecked") @Override
public DefineChainedEvent until(Predicate stopCondition) {
latest.unregisterCondition = evt -> stopCondition.test(evt);
registerIfNeeded();
return this;
}
@SuppressWarnings("unchecked") @Override
public DefineChainedEvent While(Predicate continueCondition) {
latest.unregisterCondition = evt -> !continueCondition.test(evt);
registerIfNeeded();
return this;
}
@Override
public void repeatIndefinitely() {
latest.followUp = first;
}
}
}
The ManagedObserver class looks like this:
class ManagedObserver implements Observer {
final ObserverManager source;
final EventSrc target;
Class<? extends Event> evtType;
Consumer<Event> action;
Predicate<Event> unregisterCondition;
ManagedObserver followUp;
ManagedObserver(ObserverManager source, EventSrc target) {
this.source = source;
this.target = target;
}
@Override
public void onEvent(Event evt) {
if (evtType.isInstance(evt)) {
action.accept(evt);
}
if (unregisterCondition != null && unregisterCondition.test(evt)) {
unregister();
if (followUp != null) {
followUp.register();
followUp.onEvent(evt);
}
}
}
void register() {
target.addObserver(this);
}
private void unregister() {
target.removeObserver(this);
}
}
My main concerns are:
- the naming of the methods in ObserverManager. The methods "Do" and "While" use capitalization in their names (because these words are java keywords) but in my opinion these names are the "perfect" fit for their purposes.
- feature completeness. Will I be able to use this throughout my application or will this become a huge investment for little return because it lacks the features I will need.
- I consider the ObserverManagerImpl class a rather "dirty" implementation. On the other hand, I also think that this may be okay in a situation like this where it is part of a closed system that is not supposed to be touched after it has been finished once.
What are your thoughts and opinions? All feedback and suggestions for improvement are welcome.
Edit:
I changed the ObserverManager interface & implementation in such a way to make sure both "until" methods work the same way. I guess it makes the implementation even more ugly. Advise is appreciated.