可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
In the following, I want EventHandler to handle EventA one way, EventB another way, and any other Events (EventC, EventD) yet another way. EventReceiver receives only a reference to an Event and calls EventHandler.handle(). The version that always gets called, of course, is EventHandler.handle(Event event).
Without using instanceOf, is there a way to polymorphically dispatch (perhaps via another method in EventHandler or generics) to the appropriate handle method?
class EventA extends Event {
}
class EventB extends Event {
}
class EventC extends Event {
}
class EventD extends Event {
}
class EventHandler {
void handle(EventA event) {
System.out.println("Handling EventA");
}
void handle(EventB event) {
System.out.println("Handling EventB");
}
void handle(Event event) {
System.out.println("Handling Event");
}
}
class EventReceiver {
private EventHandler handler;
void receive(Event event) {
handler.handle(event);
}
}
回答1:
Sounds like a case for applying (a variant of) the Visitor pattern. (In mainstream OO languages such as C++, C# and Java, methods are single dispatch, i.e. can only be polymorphic on one type at a time. Visitor allows one to implement double dispatch.)
This however requires that you be able to modify the Event
classes as well, and creates a dependency from Event
s to (a base interface of) EventHandler
.
class EventA extends Event {
public handleBy(EventHandler eh) {
eh.handleEventA(this);
}
}
class EventB extends Event {
public handleBy(EventHandler eh) {
eh.handleEventB(this);
}
}
class EventHandler {
void handleEventA(EventA event) {
System.out.println("Handling EventA");
}
void handleEventB(EventB event) {
System.out.println("Handling EventB");
}
void handle(Event event) {
event.handleBy(this);
}
}
回答2:
This is a use case for double-dispatch, no (which as one may indeed know is either called Visitor) ? I'll implement your example for EventA only
class Event {
/**
* Will do some type escalation
*/
void handleWith(EventHandler care) {
care.handle(this);
}
}
class EventA extends Event {
/**
* As event is EventA, this implementation is called, with its correct type forced by the cast
*/
void handleWith(EventHandler care) {
care.handle((EventA) this);
}
}
class EventHandler {
/**
* Finally comes here
*/
void handle(EventA event) {
System.out.println("Handling EventA");
}
void handle(EventB event) {
System.out.println("Handling EventB");
}
void handle(Event event) {
System.out.println("Handling Event");
}
/**
* Go here first and dispatch call to Event class
*/
void doHandle(Event event) {
event.handleWith(this);
}
}
class EventReceiver {
private EventHandler handler;
void receive(Event event) {
handler.doHandle(event);
}
}
回答3:
Java only has polymorphic dispatch on the object a method is invoked on. That means, the only way to get real polymorphism is to put the handle()
method into the Event
interface itself. I'd actually say that is the overall better and more OO solution, since a "handler" that operates on data objects is rather procedural.
Any other solution (like a map of handler objects keyed on the class) is going to be more complex and less flexible, especially concerning inheritance.
回答4:
You could use a Map and map event types to event handlers.
Map<Class<? extends Event>, Handler> map =
new HashMap<Class<? extends Event>, Handler>();
void receive(Event event) {
Handler handler = map.get(event.getClass());
handler.handle(event);
}
回答5:
I know how you can do it with some pre-processing. Use something like this:
public abstract class EventHandler<T extends Event> {
public abstract void handle(T event, Class<T> c);
public abstract Class<T> handles();
}
public class EventHandlerA extends EventHandler<EventA> {
@Override
public void handle(EventA event, Class<EventA> c) {
System.out.println(event);
}
@Override
public Class<EventA> handles() {
return EventA.class;
}
}
Then use a map to organize your handlers
HashMap<Class<?>,Collection<EventHandler<?>> handlers;
When an event needs to be handled just retrieve the handlers from the map. If Class.equals() and Class.hashCode() doesn't work how you want then you'll need a wrapper to get the behavior you want.