I’m working on a project that processes requests, and there are two components to the request: the command and the parameters. The handler for each command is very simple (< 10 lines, often < 5). There are at least 20 commands, and likely will have more than 50.
I’ve come up with a couple of solutions:
- one big switch/if-else on commands
- map of commands to functions
- map of commands to static classes/singletons
Each command does a little error checking, and the only bit that can be abstracted out is checking for the number of parameters, which is defined for each command.
What would be the best solution to this problem, and why? I’m also open to any design patterns I may have missed.
I’ve come up with the following pro/con list for each:
switch
- pros
- keeps all commands in one function; since they’re simple, this makes it a visual lookup table
- don’t need to clutter up source with tons of small functions/classes that will only get used in one place
- cons
- very long
- hard to add commands programmatically (need to chain using default case)
map commands -> function
- pros
- small, bite-size chunks
- can add/remove commands programmatically
- cons
- if done in-line, same visually as switch
- if not done in-line, lots of functions only used in one place
map commands -> static class/singleton
- pros
- can use polymorphism to handle simple error checking (only like 3 lines, but still)
- similar benefits to map -> function solution
- cons
- lots of very small classes will clutter project
- implementation not all in the same place, so it’s not as easy to scan implementations
Extra notes:
I’m writing this in Go, but I don’t think the solution is language-specific. I’m looking for a more general solution because I may need to do something very similar in other languages.
A command is a string, but I can easily map this to a number if convenient. The function signature is something like:
Reply Command(List<String> params)
Go has top-level functions, and other platforms I’m considering also have top-level functions, hence the difference between the second and third options.
8
This is a great fit for a map (2nd or 3rd proposed solution). I’ve used it dozens of times, and it’s simple and effective. I don’t really distinguish between these solutions; the important point is that there is a map with function names as the keys.
The major advantage of the map approach, in my opinion, is that the table is data. This means that it can be passed around, augmented, or otherwise modified at runtime; it’s also easy to code additional functions which interpret the map in new, exciting ways. This wouldn’t be possible with the case/switch solution.
I haven’t really experienced the cons that you mention, but I would like to mention an additional drawback: dispatching is easy if only the string name matters, but if you need to take additional information into account in order to decide which function to execute, it’s a lot less clean.
Perhaps I’ve just never run into a hard enough problem, but I see little value in both the command pattern and encoding the dispatch as a class hierarchy, as others have mentioned. The core of the problem is mapping requests to functions; a map is simple, obvious, and easy to test. A class hierarchy requires more code and design, increases the coupling between that code, and may force you to make more decisions upfront that you will later be likely to need to change. I don’t think the command pattern applies at all.
Your problem lends very nicely to the Command design pattern. So basically you will have a base Command
interface and then there would be multiple CommandImpl
classes that would implement that interface. The interface essentially needs to just have a a single method doCommand(Args args)
. You can have the arguments passed in via an instance of Args
class. This way you leverage the power of polymorphism instead of clunky if/else statements. Also this design is easily extensible.
Whenever I find myself asking whether I should use a switch statement or OO-style polymorphism I refer to the Expression Problem. Basically, if you have different “cases” for your data and wand to support different “actions” (where each action does something different for each case) then its really hard to make a system that naturally lets you add both new cases and new actions in the future.
If you use switch statements (or the Visitor pattern) then its easy to add new actions (because you wite everything in a single function) but hard to add new cases (because you need to go back and edit the old functions)
Conversely, if you use OO-style polymorphism its easy to add new cases (just make a new class) but hard to add methods to an interface (because then you need to go back and edit a bunch of classes)
In your case you only have one method that you need to support (proccess a request) but lots of possible cases (each different command). Since making it easy to add new cases is more important than adding new methods, just make a separate class for each different command.
By the way, from the point of view of how extensible things are, it doesn’t make a big difference whether you use classes or functions. If we are comparing against a switch statement what matters is how things get “dispatched” and both classes and functions get dispatched the same way. Therefore, just use whatever is more convenient in your language (and since Go has lexical scoping and closures, the distinction between classes and functions is actually very small).
For example, you can usually use delegation to do the error checking part instead of relying on inheritance (my example is in Javascript because O don’t know Go syntax, I hope you don’t mind)
function make_command(real_command){
return function(x){
if(check_arguments(x)){
return real_command(x);
}else{
//handle error here
}
}
}
command1 = make_command(function(x){
//do something
})
command2 = make_command(function(x){
//do something else
})
command1(17);
commnad2(42);
Of course, this example assumes that there is a sensible way to have the wrapper function or parent class check arguments for every case. Depending on how things go it might be simpler to put the call to check_arguments inside the commands themselves (since each command might need to call the checking function with different arguments, due to a different number of arguments, different command types, etc)
tl;dr: There is no best way to solve all problems. From a “making things work” perspective, focus on creating your abstractions in ways that enforce important invariants and protect you from mistakes. From a “future-proofing” perspective, keep in mind what parts of the code are more likely to be extended.
1
I’ve never used go, as a c# programmer I would probably go down the following line, hopefully this architecture will fit what you are doing.
I would create a small class/object for each with the main function to execute, each one should know its string representation. This gives you plugability which it sounds like you want as the number of functions will increase. Note that I wouldn’t use statics unless you really need to, they don’t give much of an advantage.
I would then have a factory which knows how to discover these classes at run time altering them to be loaded from different assembles etc. this means you don’t need them all in the same project.
Thus also makes it more modular for testing and should make code nice and small, which is easier to maintain later.
How do you select your commands / functions?
If there’s some “clever” way of selecting the correct function then that’s the way to go – means that you can add new support (perhaps in an external library) without modifying the core logic.
Also, testing individual functions is easier in isolation than an enormous switch statement.
Finally, only being used in one place – you might find that once you get to 50 that different bits of different functions can be reused?
1
I have no idea how Go works, but an architecture I have used in ActionScript is to have a Doubly Linked List that acts as a Chain of Responsibility. Each link has a determineResponsibility function (which I implemented as a callback, but you could write each link separately if that works better for what you’re trying to do). If a link determined it had responsibility, it would call meetResponsibility (again, a callback), and that would terminate the chain. If it didn’t have responsibility, it would pass the request to the next link in the chain.
Different use cases could be easily added and removed simply by adding a new link between the links of (or at the end of) the existing chain.
This is similar to, but subtly different from, your idea of a map of functions. The nice thing is that you just pass in the request and it does its thing without your having to do anything else. The down side is that it doesn’t work well if you need to return a value.