As the title suggests, is it possible to have a switch statement choose what to do, depending on the type of an object.
This gets sick, if when I have enough different objects:
if (myObject instanceof Gun) {
}
if (differentObject instanceof Cannon) {
}
..
..
..etc.
..etc.
I’m using KryoNet, that’s why.
Can I do this with a switch, or anyway more elegantly when having 120 different objects?
Maybe create a wrapper class that holds a reference to a Class and a reference to a Runnable, instantiate one wrapper for each class you need and add them to a list. Then simply loop through the list, comparing the Class reference to the class of the object
Ok, so I was thinking about this so I decided to code it up:
public class Main {
public static void main(String[] args) { new Main(); }
class A{
String aString = "State for A";
}
class B{
String bString = "State for B";
}
interface Action {
public void go(Object data);
}
Main(){
HashMap<Integer, Action> map = new HashMap<Integer, Action>();
map.put(A.class.hashCode(),
new Action(){
public void go(Object data) {
System.out.println(((A)data).aString);
}});
map.put(B.class.hashCode(),
new Action(){
public void go(Object data) {
System.out.println(((B)data).bString);
}});
Object[] objects = new Object[]{ new A(), new B(), new A(), new B()};
for(Object o : objects){
map.get(o.getClass().hashCode()).go(o);
}
}
}
The idea here is that the objects array represents your stream of incoming objects from Kryonet.
The output for the above is:
State for A
State for B
State for A
State for B
Obviously, you can register however many Class /Action pairs as you like. Make sense?
The problem here is that a ‘newbie’ (with all due respect) is learning to program and learning to do networking, with a highlevel API (kryonet).
What ever happened to DataInputStream/DataOutputStream? Learn it there, move on once you feel confident you master the basics: that is, making it work. Starting with a highlevel API is a sure way not to learn the underlying concepts of networking.
On that note: once you converted your code to DataInputStream/DataOutputStream, you obviously haven’t solved the original problem yet, but I doubt that problem will still be there. You can simply send Strings, use some refactoring to lookup a method, and invoke that (initially with a byte[] as parameter, containing the remaining bytes of the message). No need to do the switching, let java reflection do the binding for you.
Even better: a text-based protocol – much easier to debug.
My solution has the advantage that it doesn’t require you to be able modify all the passed objects (yours wouldn’t work if you were passed an object that wasn’t a Processed) … but in this case I don’t suppose that matters because Kryonet requires you to code up both halves of the client server arrangement, if I recall correctly.
interface Handler<T> {
public void call( T t );
}
class ClassActions {
private Map<Class<?>, Handler<?>> actions = new HashMap<Class<?>, Handler<?>>();
public <T> void add( Class<T> klass, Handler<T> action )
{
actions.put( klass, action );
}
public <T> void invoke( T obj )
{
Class<T> objKlass = obj.getClass();
Handler<T> handler = (Handler<T>) actions.get( objKlass );
handler.call( obj );
}
}
// setup action handlers
actions.add( Gun.class, new Handler<Gun>() {
public void call( Gun gun ) {
// gun handler
}
} );
actions.add( Bullet.class, new Handler<Bullet>() {
public void call( Bullet bullet ) {
// Bullet handler
}
} );
actions.add( Other.class, new Handler<Other>() {
public void call( Other other ) {
// some other handler
}
} );
// run handlers on an object
actions.invoke( bullet );
// run handlers on all objects stored
for ( Object o : myStuff ) {
actions.invoke( o );
}
The above is untested, but something along those lines should work. But if you could access generic parameters at runtime it could be a bit more terse.
IMO, all the solutions (including the visitor pattern) are pretty “Eeergh”! They’re all hacks around the fact that Java doesn’t do double dispatch. I’ve used all these approaches depending on the circumstances. The visitor pattern is probably the better performing (as mentioned, if you’re coding both sides of the equation), but it does involve adding warts to your API!
It may be better to use the class as the key rather than assuming the hashcode is unique (even though it should be). You could also use an IdentityHashMap. The disadvantage to using a map is that you lose polymorphism. Ie, you can only look up the concrete type. A mechanism to look up using super classes and interfaces starts to be a lot more complexity than it is worth.
KryoNet just provides you the object, because this is the most flexible. You can use instanceof (sufficient for most cases) or you can implement your own handling. Doing instanceof is very cheap. How many instanceofs are equivalent to one HashMap lookup? Either way, it isn’t likely to be a performance bottleneck. Still, you can reduce the number of ifs by ordering them to check the most likely first, grouping instanceof checks by first doing an instanceof on a super class, or implementing your own lookup. Eg, you could make a general purpose listener that uses HashMap as described above (note the classes in the follow code does not exist, you would need to write them):
DispatchListener dispatch = new DispatchListener();
conn.addListener(dispatch);
dispatch.addListener(A.class, new ObjectListener<A>() {
public void received (Connection conn, A a) {
...
}
});
...
I personally don’t find this cleaner than many instanceof checks, but it could perform slightly better if you have many types.
If the sole reason to avoid the many “if instanceof” checks is that you don’t like the syntax, you can use Listener.ReflectionListener, eg:
conn.addListener(new ReflectionListener() {
public void received (Connection conn, A a) {
...
}
public void received (Connection conn, B b) {
...
}
public void received (Connection conn, C c) {
...
}
});
This has the same drawback as using a HashMap (only looks up the concrete type) and there is a negligible performance hit (it uses cached reflection). To get around the former, you can override “public void received (Connection connection, Object object)” and do any instanceof checks you need, calling the super method for anything you don’t handle, which lets you use the individual methods to handle the rest.
I’ve not used KyroNet so this relates to the issue in general. I think it would be better to use the HashMap approach if the different handlers relate to entirely separate issues.
Using a big list of instanceof’s means you have the different checks coded together in one place. With the HashMap you can have it passed through different sections who each add their own handlers there. This allows you to have a class’s handler closer to the class itself (or even within it).
I think the best approach depends on the game structure and logic, but one approach which I can see working is a decorator-style approach (I’m not sure whether it’s GoF decorator) along the following lines:
public enum Action
{
Draw,
Paint,
etc;
}
public abstract class GameObject
{
private final Map<Action, Runnable> actions;
protected abstract void registerActions();
protected final void registerAction(Action action, Runnable command) {
actions.put(action, command);
}
public final Runnable getAction(Action action) {
if (actions == null) {
actions = new HashMap<Action, Runnable>();
registerActions();
}
return actions.get(action);
}
}
Subclasses can then call super.registerActions() in their implementations of it, use getAction in registerActions to get the superclass’ command and wrap it in another, which they then register, completely override the superclass’ command for an action, …
The main problem I see with this approach is that it’s very theoretical - it’s nicely OO (albeit more Smalltalk-inspired than pure Java), but it involves a lot of anonymous inner classes and dispatch overhead, and sometimes OO is bad because it makes it hard to follow instruction flow.
…whereas my way is ultra-simple, and as fast as it is possible to get Which might be significant if you’re doing say 5000 of them per frame. Or whatever.
Yours requires adding an interface to the class of every object being handled. You might not always be able to do this and makes the code more bloated then the other solutions.
While normally this question screams polymorphism as the answer, if there are 120 different classes then I would say the design is suspect.
Time to take off your OO hat and put on your data driven hat.
I would investigate a way to roll all the commonality into a base class which would store a single int or enumeration. Then instead of switching your code should be able to do what it needs to do via array indexing off that int.