There's something wrong with this approach, though: it couples ExitMessage and BasicActor, in that BasicActor would have to have a method for each event type, resulting in lots of empty methods, etc.
What I'm describing is a marker interface for eventing, with specific subtype APIs:
interface LifecycleListener { }
interface StartListener extends LifecycleListener {
void onStart(Foo f);
}
interface ExitListener extends LifecycleListener {
void onEnd(Foo f);
}
along with a single registration method (perhaps a LifecycleObservable API, or in a class also offering event-firing methods):
Disposable addLifecycleListener(LifecycleListener l)
The benefits to the client code are numerous: clarity (dedicated methods for events), declarative code ('implements' section documents interactions), performance (no dispatching in client code).
[Note, Disposable here represents a dispose() function to deregister the listener, avoiding the classic pair of void register/deregister methods. Often I find void methods represent a missed opportunity ... wishing Java was more like Smalltalk here]
The service side has to jump through some hoops to efficiently dispatch, using class equality or instanceof at registration time to pre-select listeners. This code could certainly use pattern-matching but personally I think it would look identical. If anything, I'm glad that pattern-matching isn't available in this case, to at least alert framework devs to the problem and not push it off to tons of switching on the client.