|
IntroductionI think every server source I've ever looked at has a 3k lines class with a huge switch or if else if code block on it, weather it's the commands class, the object or NPC handler classes, they all have one of these ugly ass mega code blocks and it bugs the shit out me. I wanna know what do you guys think and the reason behind this commonly used technique, what are the workarounds you guys have use, what other ways there are to handle a huge amount of content.
My ExperienceTo share a little of my own experience, I'm currently working on a project over the Matrix3 source and the 3000 lines commands class simply annoyed me so much that deleted it entirely and made a new class with a different way of dealing with commands. Instead of having a huge class with the code for every command nested on a switch case case case case case statement, I used something called "Command pattern". I created one little class with 4 different Command hashmaps and distributed the commands code in 4 other classes depending on the nature of the command (player, moderator, administrator and debug command classes). Each with a method that is called on initialization which introduces the commands into the respective hashmap using lambda expressions.
Example:
Code:CommandHandler.addCommand("players", 0, (player, args) -> player.getPackets().sendGameMessage("There's currently " + World.getPlayers().size() + " players playing " + Settings.SERVER_NAME));
Then when the user attempts to execute a command, the server simply searches for it on the corresponding hashmaps and executes it's code.
One really nice advantage of this method is the generic "commands" command, if you use a switch/else if cascade you are going to have to hard-code a list of every command manually, while if you use hashmaps the "commands" command becomes really simply to code. On the other hand, this method might have a big disadvantage, efficiency. I don't really know weather HashMap.Get() is faster than switch statements or not, I've done some research but couldn't get a clear answer, but since there's rarely more than 100 commands used in a minute (depending on player base) I don't think performance is a big factor on this case, but it is on other stuff like NPC's, item clicks, etc. where the frequency of use is much bigger.
The classes still get big with each command added, but they can be distributed in as many classes as wanted and can make small commands shrink down to a single line. Which all adds up to more maintainability and ease of reading
ConclusionSo what do you guys think about the horribly looking switch and if else mega blocks? Do you think command pattern could be implemented to other classes like NPCHandler, ObjectHandler, etc. without losing performance?
PS: If anyone is interested of implementing command patter on their server, just read this wiki article
That's literally one of the first things I changed when I started using the matrix framework, I looked into commands.java and thought to myself, "yeah fuck that.. *deletes Commands.java*"
Right now commands and stuff that used to go into 'ObjectHandler' etc are put into our plugin system. I haven't done any benchmarks on performance (probably should) but we have quite a few plugins and there is no visible performance difference when playing the game.
That plugin system looks really nice. I hate to have code for stuff that has nothing to do with each other all in the same class, like objects. I might implement something like what you guys did, but then I'd have to deal with re-adding all the item/objects that matrix already has >.<. Some code could be made to port it all over and save time tho
Let me just say that I think your plugin system looks delicious. As you mentioned, I'm not sure about the efficiency of using a plugin system over switch or else/if statements, but I do remember reading a study that JavaScript (or probably inherently a scripting language of some sort) is a matter of milliseconds faster than hard-coded Java. I would, however, take that with a grain of salt, as I can't remember where that study came from lol. I would imagine that script would execute faster than traditional java, but I can't back that up with firsthand knowledge, so reh.
I had a question tied to this post, but I figured asking it in your project thread would be more appropriate, seeing as other users might be interested in it as well. I'll be anxiously awaiting your answer!
Last edited by Jintishi; 09-29-2016 at 04:11 AM. Reason: moved the question to the project thread
Efficiency doesn't only have speed as a factor; maintenance is just as important. Having a huge block of conditional statements is clearly not very maintainable, and hence defining content in a modular fashion is better. Speed isn't an issue either, since the RS clock works on 0.6 seconds, we have a large gap to process whatever.
Lookup is also probably o(1), and the only time where speed would matter is when it takes ridiculously long to obtain the interaction, such as when using reflection. So in short, the performance loss is negligible, and the usability of defining content separately is much better (prettier to look at, easier to maintain, easier to define in the first place, etc)
It's part of the things I plan on cleaning before I actually code more content. There's so many if else in the handlers, I feel like I'm gonna die of cancer everytime I go in there. At least I respect the creators for making up so much content.
Tip for object/item interaction: There should probably be a constructor for both id and name. Sometimes it's easier to apply an action to all the items with the same name. To not reduce performances with something like this:
You would probably have to convert these interactions:Code:private boolean checkNameInteractions(String itemName) { for (ItemInteraction ii: nameItemInteractions) { if (itemName.contains(ii.getName()) { ii.execute(); return true; } } return false; }
What do you guys think?Code:private void hashNameInteractions() { for (ItemInteraction ii: nameItemInteractions) { for (int i = 0; i < Utils.getItemDefinitionsSize(); ++i) { ItemDefinitions defs = ItemDefinitions.getItemDefinitions(i); if (defs.getName().contains(ii.getName())) { if (itemInteractions.get(i) == null) { itemInteractions.put(i, ii); } else { System.out.println("Error: 2 item interactions overwriting each other for " + item.getName() + "(" + ii.getName() +")"); } } } } }
Well yea but with only an int, wouldn't you have to do something like this for multiple Ids?:
Even this would be kind of meh, wouldn't it:Code:itemInteractions.add(new Interaction(id1, new TimerTask @Override public void run() { //code1 //code2 //code3 } )); itemInteractions.add(new Interaction(id2, new TimerTask @Override public void run() { //code1 //code2 //code3 } ));
Code:WorldTask useTeleport = new TimerTask @Override public void run() { //code1 //code2 //code3 } ); itemInteractions.add(new Interaction(id1, useTeleport)); itemInteractions.add(new Interaction(id2, useTeleport));
In industry 50+ lines of code in a single method is considered bad design.
Plugins is a nice way to make your code extensible and allows for easy addition/deletion of content. That's why it's important to design a nice foundation to build your code on. Interfaces are also useful because they allow for multiple inheritance and can force your team to follow a design.
« Previous Thread | Next Thread » |
Thread Information |
Users Browsing this ThreadThere are currently 1 users browsing this thread. (0 members and 1 guests) |