Design pattern for a large nested switch statement

2020-03-03 07:16发布

问题:

I've searched for a number of articles on refactoring a large switch statement.

But they don't do what I want to do. The problem I'm going to to run in to is having a gigantic switch statement which calls a different method depending on two different values, lets say a type and a code.

Currently, I would handle the situation like this:

switch (type)
{
    case Types.Type1:
        handleType1(code);
        break;

    case Types.Type2:
        handleType2(code);
        break;
}

void handleTypeN(code)
{
    switch (code)
    {
       ...
    }
}

Maybe something which combines the factory and command pattern would help me out? I must be missing something obvious.

How would you refactor this code?


I might need to get a little more specific as to what scenario I'm facing.

I'm receiving packets from a server. A packet contains a type and a code and some specific information.

As soon as data arrives I retrieve the type and the code of the packet and it goes in to the switch statement for the type, after figuring out the type a specific method is called to perform a switch on the code of the packet.

The method that handles the code now decodes the packet further and the process is done.

+----------+                +----------+
|          |     Packet     |          |
|  Server  | -------------> |  Client  |
|          |                |          |
+----------+                +----------+
                                  |
                                  |
         (Switch on the type of the packet and call a specific method)
                                  |
                                  |
         (Switch on the code of the packet and call a specific method)
                                  |
                                  |
                    (Respond to the server or not)

回答1:

I think it depends what kind of code improvement you're trying to do.

If you have the luxury of actually making big design changes, then I'd suggest polymorphism:

Create an abstract Packet class.
Create a class for each packet type.
Create a factory method, that receives a raw server packet, and creates the right packet class object.
Each packet class type will have its own implementation of the job it needs to do.

If you don't have the luxury of doing large design changes (which is often the case):

  • If you want to improve readability :

Keep the switch, each switch case will call a properly named function that will do what it needs to.

  • If you want to increase performance:

Create a matrix, that for each cell [T,C] will hold a reference to a function that will handle a Packet with Type T and Code C.
The matrix should be initiated once (hard-coded, no way around that) at startup of program or class.
This will give you better performance than a long switch block (direct access to code, no logical comparisons)



回答2:

2 pattern comes in mind : command and visitor : http://en.wikipedia.org/wiki/Command_pattern http://en.wikipedia.org/wiki/Visitor_pattern

Abstract Class Command {

 executeSomething();

}
Class typeN extends command {

   executeSomething() {
    //...
   }
}

Class typeM extends command {

   executeSomething() {
    //...
   }
}

replace your switch by :
//my old switch
class commandManager {

processCommand() {
for listOf command in a command buffer
{ 
 onCommand(listOfcommand.command)
}
}
 onCommandPoped(command type) {
  type.executeSomething()
 }

}

you can pass parameters to executeSomething, and you can pass another command


the client code : 

{
 commandN = new CommandN()
 commandManager.postCommand( commandN)
}

After reading your packet server use case, I think you can use a variant of strategy pattern http://www.oodesign.com/strategy-pattern.html where you choose the strategy to call when the packet arrive you can build this with a factory.

but you will not kill your switch case

Keep in mind, that a server can serve many client. If it's maybe your switch cases are faster than object instanciation.



回答3:

I'd build a table with types which points to tables with codes, which in turn points to the implementing function to call for that type/code pair.

lookup_type_table[type_low .. type_high] = { lookup_code_table_type_1, lookup_code_table_type_2, ...};
lookup_code_table_type_1[type_1_code_low .. type_1_code_high] = { type1_code1_func_pointer, ... };

int processPacket(int type, int code, paket_t data) {
  /* apply boundary checks on the lookup tables here! */
  return lookup_type_table[type][code](data);
}

The tables could be implemented as linked lists, or with some other fancier container implementation to make them more dynamic, if needed.

This might not be as object-oriented as some other patterns, but I'm coming from a C-background :)

Having the mappings in tables adds the possibility to generate them from some other DSL spec though, boosting your DRY stats...



回答4:

You can replace your switches with a dictionary of dictionaries which points to the actual methods or better classes to handle the packets. The first dictionary using type as index and the second using code as index.

But then you will have replaced the switch statements by the creating of the dictionaries, so in the end there isn't much difference.

You should have a look to the state pattern too, this kind of stuff is what its actually made for.