Why do you need to extend this class? And why do you need to name your own method the same as showDialog
?
In reality your method does something entirely different than what showDialog
does. A better name for your method would be showDialogAtLocationAndReturnSelectedFile
as your method does more or less these things. Naming it showDialog
will only confuse your code users.
Also, without knowing anything else, I'd say you're trying to shove too much in a single method. How do you react on a cancel press? How about an error? Do you return null? If so, you're forcing the user of the code to check the return value yet again. This has the potential of being just another "Leaky Abstraction", and Java APIs already have enough of these.
An important part of API design is making sure that the name of a function/class/method matches what it really does. And that is why in JFileChooser
the method's name is showDialog
. It just shows the dialog. It doesn't open the file for reading, it doesn't perform a check whether the filename is valid, and honestly, why would it? The user of the code just asked the class to show the dialog.
The creator of Ruby calls this the 'Principle of Least Surprise'*, and while I don't really know Ruby, this is a great line to learn from. Your code should be in the service of its user, and a part of this service is embedding the contract of the method/class in its name.
You might think you're not designing an API, but I doubt you work alone: there's probably someone else in the team, and they will appreciate this. Also, I heartily recommend this lecture on API Design: How To Design A Good API and Why it Matters. It was presented to Googlers by a Java designer, so it kinda matters.
Maybe this is more than you asked for, but I feel you seem to be somewhat missing the point of naming methods.
UPDATE: * I seem to be mistaken, the creator of Ruby has actually stated that he designed Ruby with the "Principle of Least Astonishment", not "Principle of Least Surprise". In any case, what I said still holds.
I fully realize that I am 2 years late to the party :-) but probably few clarifications will help other devs.
What the other answers missed to address was the mentioning of the "Kryonet" which adds additional twist to the problem: usually when kryonet is used it means that
a) the client is an android app and
b) the client is some game
If both conditions are met IMHO using the instanceof if-else sequence is the way to go.
The usual way of structuring client/server app in our case android game + "pure" java server is to use (at least) 3 projects:
- "common" - which contains code common for the server and the client, most importantly all the message classes (which usually implement some tag/marker interface as
GameMessage
).
- server - contains the server and includes the
common
project. Usually all messages are simply registered in Kryonet, i.e. no pooling is used (because on server GC is not considered a problem)
- android client - contains the android app (game) and includes the
common
project. Because in java games creating objects during the game may trigger the GC it is considered the biggest mortal sin (which leads straight to the hell of missed frames) it is considered good practice1 to use object pooling for the game messages (this requires little customization on the Kryonet/kryo in order to use object pools). All messages are (must be) registered in kryonet in the same order as on the server in order things to work.
First, lets look at the alternatives and see why they are not fit for the structure/implementation described above:
1. They say 'Use polymorphism and method overriding'
Take a look here for an example of the proposed solution (accepted answer by jmg). It looks very elegant but there there are at least 2 big problems:
You can't just use parameterless overrided method like do()
because in real implementation you will some need external data to act upon. For example when receiving PlayerAbandonedGame
message on the server you will need at least the particular game object as parameter in order to call removePlayer(Player pl)
method. NewGameState
message will require reference to the particular GameState
variable that will be updated and so on. One very ugly solution is overrided method to have single parameter like TheWholeThing
which contains all the objects that may be needed. It is needless to say that this not only violates the principle of encapsulation but also "spreads" the game logic across multiple message classes.
Remember that we have the common
project and all the messages are defined there. Even if server-to-client messages are completely distinct set to client-to-server messages it will be simply stupid to put server or client logic in the common
project.
Game message objects are just data containers. It is up to the server or the client to handle that data in a particular way (or even ignore it as often happens with out of order UDP messages).
2. They say 'Use reflection'
Our context is 'a) the client is an android app'. The reaction of every android developer hearing Use reflection
should be to stand up, clear his/hers throat and with his/hers best british accent to say: 'Are you completely mAd?!' :-)
Reflection is very slow on older Android versions, it is slow even now (5.0). Also IMO using reflection is the ugliest possible solution not just for this particular problem. It should be avoided up in the stack, i.e. in app dev.
Way to go
Back to the original question: if one really insist on using switch
the solution is to have base class like:
public abstract class GameMessage {
byte mCode; // not private because of kryo
public GameMessage(byte code) {
super();
mCode = code;
}
public byte getCode() {
return mCode;
}
}
concrete message will look like:
public class PlayerAbandonedGame extends GameMessage {
// some fields
public PlayerAbandonedGame() {
super((byte) 42);
}
}
and then message handler will look like:
public synchronized void onMessageReceived(GameMessage msg) {
switch(msg.getCode()) {
case 42:
// do something
break;
default:
mLogger.error("Unknown message code {}", msg.getCode());
// may be even throw an exception?
break;
}
// ...
}
Of course it is advisable to use enum values instead of hardcoded numbers...
Potential drawback is (as mentioned in other answers) if you create new message class and forget to add it to some of the switch statements. The compiler cannot catch this problem and you will hit it only runtime (in the default
case).
Real (small) drawback is that code
field is send each time and adds to the traffic. In reality this adds about 1-10% to the size of the messages (if you use byte
for the code field).
How I do it
I personally use if-else sequence with instanceof test like:
public synchronized void onMessageReceived(GameMessage msg) {
if (msg instanceof MyLetterAttemptResult) {
handleMyLetterAttemptResult((MyLetterAttemptResult) msg);
mPools.release(msg);
} else if (msg instanceof InAutoMatchQueue) {
handleInAutoMatchQueue((InAutoMatchQueue) msg);
} else if (msg instanceof AutoMatchRetry) {
handleAutomatchRetry();
// more else-ifs
} else {
mLogger.error("Unknown message {}", msg.getClass().getSimpleName());
// throw exception may be?
}
}
Isn't that "code smell"? You bet it is, but after trying all other alternatives it seems to me that it is best. To reduce the smelliness it is better to try to keep code in the ifs short like above. Just call handle* method and release message back to the pool (if it is pooled).
Isn't that slow? It is a bit slower than the switch(msg.getCode())
but not that much. Here is some benchmarking (answer by Dan Mihai Ile). Advantage is that you don't have the code
field send each time over the network. Keep the most frequently used messages at the top so they are found first.
Isn't that error prone? There is a risk if you create new message class and forget to add corresponding else if
. The thing is that you have just two separate message handling methods - one on the server and one on the client. Both are the single most important code pieces repectively on the server and the client. You will simply have to not mess it up. Just add the new message class immediately to the unit test that tests onMessageReceived()
.
This is not OOP! I have a better solution! I will be very happy to discuss it and use it :-). btw, I wonder if @Cameron (the author of this question) found better solution. After all this question is 2 years old...
1 For me avoiding the GC is more like "utter crap" than "best practice". As Eonil commented in 1: I regard avoiding GC on GC based language is a kind of insane trial.
Best Answer
It doesn't always make sense to add functions to the base class, as suggested in some of the other answers. Adding too many special case functions can result in binding otherwise unrelated components together.
For example I might have an
Animal
class, withCat
andDog
components. If I want to be able to print them, or show them in the GUI it might be overkill for me to addrenderToGUI(...)
andsendToPrinter(...)
to the base class.The approach you are using, using type-checks and casts, is fragile - but at least does keep the concerns separated.
However if you find yourself doing these types of checks/casts often then one option is to implement the visitor / double-dispatch pattern for it. It looks kind of like this:
Now your code becomes
The main benefit is that if you add a subclass, you'll get compile errors rather than silently missing cases. The new visitor class also becomes a nice target for pulling functions into. For example it might make sense to move
getRandomIngredients()
intoActOnBase
.You can also extract the looping logic: For example the above fragment might become
A little further massaging and using Java 8's lambdas and streaming would let you get to
Which IMO is pretty much as neat looking and succinct as you can get.
Here is a more complete Java 8 example: