Solved Better organizing of class

Discussion in 'Plugin Development' started by Tecno_Wizard, Nov 27, 2014.

Thread Status:
Not open for further replies.
  1. Offline

    Tecno_Wizard

    My class is an absolute mess. Due to its nature (Buying Commands), it is tons of if statements.
    Without saying more, IT IS A MESS.

    Code:java
    1. package me.Tecno_Wizard.CommandsForSale.commandProcessors;
    2.  
    3. import java.util.ArrayList;
    4. import java.util.HashMap;
    5. import java.util.List;
    6. import java.util.UUID;
    7.  
    8. import me.Tecno_Wizard.CommandsForSale.core.Main;
    9. import net.milkbowl.vault.economy.Economy;
    10. import net.milkbowl.vault.economy.EconomyResponse;
    11.  
    12. import org.bukkit.ChatColor;
    13. import org.bukkit.Sound;
    14. import org.bukkit.command.Command;
    15. import org.bukkit.command.CommandExecutor;
    16. import org.bukkit.command.CommandSender;
    17. import org.bukkit.entity.Player;
    18. /**
    19. * Handles the purchase of commands when the economy has loaded successfully. Handles /buycmd, /confirm, /deny
    20. * @author Ethan Zeigler
    21. *
    22. */
    23. public class BuyCmdExecutor implements CommandExecutor
    24. {
    25. private HashMap<UUID, String> awaitingConfirm = new HashMap<UUID, String>(); //stores players that need a /confirm or /deny response
    26. Main main;
    27. Economy econ;
    28. String pluginPrefix;
    29.  
    30. //handles /buycmd, /confirm, /deny
    31.  
    32. public BuyCmdExecutor(Main main, Economy econ)
    33. {
    34. this.main = main;
    35. this.econ = econ;
    36. pluginPrefix = main.getConfig().getString("PluginPrefix");
    37. }
    38.  
    39. @Override
    40. public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args)
    41. {
    42. if(sender instanceof Player)
    43. {
    44. if(cmd.getName().equalsIgnoreCase("buycmd"))
    45. {
    46. if(args.length == 1)
    47. {
    48. args[0] = args[0].toLowerCase();
    49. if(awaitingConfirm.containsKey(((Player) sender).getUniqueId()))//if player is already prompted for confirmation
    50. {
    51. sender.sendMessage(String.format("%s[%s] You are currently awaiting confirmation for another command. %s/confirm %sor %s/deny %sfirst.", ChatColor.RED, pluginPrefix, ChatColor.GOLD, ChatColor.RED, ChatColor.GOLD, ChatColor.RED));
    52. }
    53. else//if not awaiting confirmation
    54. {
    55. if(main.getConfig().getStringList("MainCommands").contains(args[0]))//if the command that is trying to be purchased is purchasable
    56. {
    57. if(!main.getSave().getStringList(((Player) sender).getUniqueId().toString()).contains(args[0]))//if the player does not already have the command
    58. {
    59. if(sender.hasPermission("cmdsforsale." + main.getConfig().getString("CommandOptions." + args[0] + ".permission")) || main.getConfig().getString("CommandOptions." + args[0] + ".permission") == null)
    60. {
    61. double price = main.getConfig().getDouble("CommandOptions." + args[0] + ".price");//gets the price of the command
    62.  
    63. if(econ.getBalance((Player)sender) >= price)//if the user has enough money to buy the command
    64. {
    65. sender.sendMessage(String.format("%s[%s] Do you want to buy the command %s%s %sfor %s%d %s%s? %s\n/confirm or /deny", ChatColor.YELLOW, pluginPrefix, ChatColor.GOLD, args[0], ChatColor.YELLOW, ChatColor.GOLD, price, ChatColor.YELLOW, econ.currencyNamePlural(), ChatColor.YELLOW));
    66.  
    67. awaitingConfirm.put(((Player) sender).getUniqueId(), args[0]);//adds to confirmation list
    68. }
    69. else //if the user is a poor old man (does not have enough money)
    70. {
    71. sender.sendMessage(String.format("%s[%s] You do not have enough %s to buy this command", ChatColor.RED, pluginPrefix, econ.currencyNamePlural()));
    72.  
    73. }
    74. }
    75. }
    76. else//if the player already has the command
    77. {
    78. sender.sendMessage(String.format("%s[%s] You already have this command", ChatColor.AQUA, pluginPrefix));
    79.  
    80. }
    81. }
    82. else //if the requested command is not purchasable
    83. {
    84. sender.sendMessage(String.format("%s[%s] That command is not purchasable. If you are typing in an aliase of the main command, such as bal instead of balance, it will not be recognized.", ChatColor.RED, pluginPrefix));
    85. }
    86. }
    87. }
    88. else//if there was not a single argument
    89. {
    90. sender.sendMessage(String.format("%s[%s] The correct usage is %s/buycmd <Command Name>", ChatColor.YELLOW, pluginPrefix, ChatColor.GOLD));
    91. }
    92. }
    93.  
    94. ////////////////////////////////////////////////////////////////////////////////////
    95. ////////////////////////////////////////////////////////////////////////////////////
    96.  
    97. else if(cmd.getName().equalsIgnoreCase("confirm"))
    98. {
    99. if(awaitingConfirm.containsKey(((Player) sender).getUniqueId()))
    100. {
    101. String toBuy = awaitingConfirm.get(((Player) sender).getUniqueId());
    102. awaitingConfirm.remove(((Player) sender).getUniqueId());//plyaer removed from awaiting confirmation
    103. double price = main.getConfig().getDouble("CommandOptions." + toBuy + ".price");
    104. if(econ.getBalance((Player)sender) >= price)//double check of if player can afford
    105. {
    106. EconomyResponse eResponse = econ.withdrawPlayer((Player)sender, price);//takes out the money (if it can)
    107. if(eResponse.transactionSuccess())
    108. {
    109. sender.sendMessage(String.format("%s[%s] You bought the command %s!", ChatColor.GOLD, pluginPrefix, toBuy));
    110. ((Player) sender).playSound(((Player) sender).getLocation(), Sound.CHICKEN_EGG_POP, 1, 5);
    111. if(!main.getSave().contains(((Player) sender).getUniqueId().toString()))
    112. main.getSave().set(((Player) sender).getUniqueId().toString(), new ArrayList<String>());//if the player did not have a save section, one is created
    113. main.getSave().saveConfig();//save that config
    114.  
    115. List<String> boughtCmds = main.getSave().getStringList(((Player) sender).getUniqueId().toString());//commands to be given to the player
    116. boughtCmds.add(toBuy);//adds main command
    117.  
    118. main.getSave().set(((Player) sender).getUniqueId().toString(), boughtCmds);
    119. List<String> alises = main.getConfig().getStringList("Aliases." + toBuy);//gets the aliases of the command
    120. for(String alis: alises)
    121. {
    122. boughtCmds.add(alis);
    123. }
    124. main.getSave().set(((Player) sender).getUniqueId().toString(), boughtCmds);
    125. main.getSave().saveConfig();
    126. }
    127. else
    128. {
    129. sender.sendMessage(String.format("%s[%s] Purchase Failed: %s", ChatColor.RED, pluginPrefix, eResponse.errorMessage));
    130. }
    131. }
    132. else //if player cannot afford
    133. {
    134. sender.sendMessage(String.format("%s[%s] You cannot afford this!", ChatColor.RED, pluginPrefix));
    135. }
    136. }
    137. else//nothing awaiting to be confirmed
    138. {
    139. sender.sendMessage(String.format("%s[%s] You are not trying to purchase any command", ChatColor.RED, pluginPrefix));
    140. }
    141.  
    142. }
    143.  
    144. //////////////////////////////////////////////////////////////////////////////////
    145. //////////////////////////////////////////////////////////////////////////////////
    146. else//will be deny
    147. {
    148. if(awaitingConfirm.containsKey(((Player) sender).getUniqueId()))//if awaiting confirmation
    149. {
    150. sender.sendMessage(String.format("%s[%s] Purchase cancelled", ChatColor.GRAY, pluginPrefix));
    151. awaitingConfirm.remove(((Player) sender).getUniqueId());
    152. }
    153. else//if not awaiting confirmation
    154. {
    155. sender.sendMessage(String.format("%s[%s] You are not trying to purchase any command", ChatColor.RED, pluginPrefix));
    156. }
    157. }
    158. }
    159. else//if the sender is the console
    160. {
    161. sender.sendMessage(String.format("[%s] Silly console, buying commands is for players!", pluginPrefix));
    162. }
    163. return true;
    164. }
    165. }
    166.  


    Does anyone know of a more efficient way to do this? I'm desperate.
     
  2. Tecno_Wizard
    I prefer not using else statements. Rather I do it like this:
    "normal" if-else statement
    Code:
    if(condition) {
        // stuff
    } else {
        // send your 'else' message
    }
    My preferred way:
    Code:
    if(!condition) {
        // send your 'else' message
        return;
    }
     
    
    // stuff
    In my opinion it doesn't make the code look so chaotic.
     
    Hawktasard and Avygeil like this.
  3. Offline

    Rocoty

    Alright. I've looked at your code, and frankly it can be greatly improved with some proper use of OOP. You should be able to reduce your onCommand method to a mere ten lines or so using what we all know and love: Abstraction. You should also apply the DRY principle. I'll give you some tips to get you going.

    What I would do, is make an interface called BuyCommandHandler (or something similar). This interface should declare one method:
    Code:java
    1. void perform(Player sender, boolean awaitingConfirm, String arg);

    With this new interface created, you can now declare a Map<Command, BuyCommandHandler> in your BuyCmdExecutor. What you should also have in your BuyCmdExecutor is a method to register BuyCommandHandlers. Something along these lines:
    Code:java
    1. public void registerCommand(Command cmd, BuyCommandHandler handler) {
    2. //Put the cmd and handler in the newly created map
    3. cmd.setExecutor(this);
    4. }

    With this in mind, you no longer need to set the executor from the main class, and this method could work as a replacement for that. Nifty.

    Still, not quite satisfied. We still need to handle these commands. Your onCommand method needs an overhaul. You could keep the instanceof Player check, since that's just a guard, but all the concrete implementation has to go (or rather, be moved somewhere else). What you should do is create new classes that implement the BuyCommandHandler interface. One class per command. When implementing the perform method, know that the sender argument is the player who sent the command, awaitingConfirm is, well, whether the player is awaiting confirmation from a previous execution of /buycmd, and arg is any extra argument that may be needed for the command (namely the arg when using /buycmd).

    You could now move the implementation of each command from the onCommand method, to each of your new classes respectively, making any necessary adjustments for the transfer. What's important here is that each command does not need to manually check the awaitingConfirm Map, as the information is already given to them by the onCommand method. Let's make the onCommand method actually do that.

    You should now have moved a lot of implementation away from the onCommand method, and it probably contains a lot of empty code blocks. Know that all your onCommand method needs to do is this:
    • Check if the sender is a player. (You already have that)
    • Check the length of args (needed for a later point)
    • Check if the sender is in the awaitingConfirm Map (also needed for a later point)
    • Get the BuyCommandHandler mapped to the command that was passed to onCommand
    • Execute the perform method of said handler with these arguments:
      • sender (the sender of the command, as a player)
      • awaitingConfirm (whether or not the player was present in the awaitingConfirm map)
      • arg (the first argument passed to the command, if one exists. null otherwise)
    It would look something like this:
    Code:java
    1. BuyCommandHandler handler = handlers.get(cmd);
    2.  
    3. Player ply = (Player) sender
    4. boolean awaiting = awaitingConfirm.containsKey(ply.getUniqueId())
    5. String arg = args.length >= 1 ? args[0] : null;
    6.  
    7. handler.perform(ply, awaiting, arg);

    I also see your commands are altering the awaitingConfirm map, which they wouldn't directly be able to if they are in different classes. A simple solution to this is to declare public methods to remove and add players from the Map in the BuyCmdExecutor class, and make sure the handlers have a reference to the BuyCmdExecutor instance. An even better solution (and the one I'd personally go with) would be to make the perform methods return a result code (likely an enum constant) and make the onCommand method check the result code and act appropriately according to the returned code. Example:
    Code:java
    1. public enum BuyCommandResult {
    2.  
    3. CONFIRM_ADD,
    4. CONFIRM_REMOVE,
    5. NONE;
    6.  
    7. }

    Code:java
    1. //NB: This is in the onCommand method
    2.  
    3. BuyCommandResult result = handler.perform(ply, awaiting, arg);
    4. switch (result) {
    5. case CONFIRM_ADD:
    6. //Put player and arg into the map
    7. break;
    8. case CONFIRM_REMOVE:
    9. //Remove player from the map
    10. break;
    11. }


    Wow. Got a bit excited there. I've given you some instructions and some code, and some might call this spoonfeeding. I don't. This is an opportunity for you to learn something. The most important thing for me when you read this is that you don't just copy my instructions and code and forget why you do it. Please, if you're copying code, know why the code does what it does.

    If you have any questions I'll be happy to help!
     
    fireblast709 and Assist like this.
  4. Offline

    Tecno_Wizard

    Rocoty
    Absolutely amazing. Thank you so much! One issue I found though, setExecutor() is not a method of Command. Besides that, spoon feeding is only spoon feeding when you can't learn from it. I will definitely use this format in the future.
     
  5. Offline

    Rocoty

    Tecno_Wizard Ah, yes. That should be PluginCommand then. A mistake on my part.
     
  6. Offline

    Tecno_Wizard

    indyetoile likes this.
Thread Status:
Not open for further replies.

Share This Page