What's the best alternative to an out of control switch statement?

可紊 提交于 2019-12-04 19:34:08

问题


I have inherited a project that has some huge switch statement blocks, with some containing up to 20 cases. What is a good way to rewrite these?


回答1:


Why would you want to rewrite them in a different structure? If you really have 20 cases that have to be handled individually, a switch/case is the way to go. A big chain of if/then logic would be horrible to maintain.

Polymorphism is another option if you are using an object-oriented language. Each subclass would implement it's own functionality in the method.




回答2:


Polymorphism. But it may not be a trivial refactoring.

Some examples and refs:

Refactoring (Googe books)

Switch Statement code smell and Polymorphism

Refactoring switch-statements




回答3:


As others have pointed out, it depends on the switch statement. However, in the past I have refactored switch statements by proceeding in the following way. Suppose we have a switch statement like this, with a lot of repeating code

switch(x){
   case 1:
     makeitso("foo");
     globalLog += "foo";
   case 2:
     makeitso("bar");
     globalLog += "bar";
   case 3:
     makeitso("baz");
     globalLog += "baz";
   ...
   default:
      throw("input error");
}

the first thing to do is to recognize the parts that are common (in the real world, this will probably be a little more substantial)

makeitso([some string]);
globalLog += [some string];

and turn that into a function

function transformInput(somestring) {
     makeitso(somestring);
     globalLog += somestring;
}

then for the parts that change in each case, use a hash or an array;

var transformvalues = ["foo", "bar", "baz"];

from here we can do this:

var tvals = ["foo", "bar", "baz" ... ];
function transformInput(somestring) {
     makeitso(somestring);
     globalLog += somestring;
}
var tval = tvals[x];
if(tval!==undefined) {
     transformInput(tval);
} else {
    throw ("invalid input");
} 

And with tvals factored out of the switch statement, it could even be provided externally to expand the number of cases you can handle. Or you could build it dynamically. In the real world, the switch statement will often have special cases, however. I leave that as an excercise for the reader.




回答4:


Three suggestions (echoing some already given):

  1. Maybe a switch isn't as bad as you think. It can be ugly if the case blocks are large. Shorten them by extracting the logic into methods.

  2. In an OO language, polymorphism might be the answer, as many have pointed out.

  3. In a functional language, like Javascript, write a function that returns the function you need to run for whatever input. That might use a switch statement itself, or it might use a lookup table.




回答5:


You could always use a lookup table.




回答6:


There's nothing wrong with having 20 cases in a switch statement. You can tidy the code by refactoring and, at the very least, move the case processing into methods/functions.




回答7:


Depending on what the switch statement is evaluating, you may want to refactor it using the Strategy Pattern. Take a look at this post for an example of replacing a switch on enum values with separate classes to handle each function.

It also may be that using the switch with 20 cases may actually be the best course of action for it. As long as it's readable and each result clearly conveys what the action is there's no need to really refactor it.




回答8:


In general, I think you should refactor only when you need to, such as when you want to add more features, but the current design isn't up for the task. You should then refactor without adding the new functionality, and only then add the new feature.

In other circumstances, don't bother with refactoring. Do it when you need to, otherwise there are probably more important things to do.

If you really really need to, then the Visitor Design Pattern is a common switch-case replacement, though you should note it does have drawbacks. (i.e., check out www.objectmentor.com/resources/articles/acv.pdf)




回答9:


It depends what the switch statement is doing.

If it's matching characters or strings, say in a parser, and you don't have the same set of patterns repeated everywhere in the code, then a switch statement might be ok.

If it's matching (say) an integer against a list of allowed values, you can create a base class and a set of derived classes for each value. Then, whatever generates the integer data in the first place can create an instance of the derived class with all of the switch statement "answers" instead.

A third option is to create a data structure that maps patterns to actions (i.e., functions or objects with virtual methods). You can look up the switch value in this data strucutre, and execute the appropriate action.




回答10:


if it's working without major bugs (by not major I mean they don't make you pull your hair out) why bother refactor it? Don't refactor everything.

If you want, you can change it to polymorphism, but this will be a shotgun surgery, you'll probably have to refactor a whole lot more than just this switch block.




回答11:


Visit https://github.com/Pedram-Ahmadpour/Switch-Case

Client side

Create an instance of your Condition, then pass a condition to Condition object by Switch() function.

    int sense = 2;
    ConditionSense conditionSense = new ConditionSense();
    conditionSense.Switch(sense);

Server side

  1. Create a condition action. This interface defines how to execute a Condition.

    public interface IAction
    {
        void Do();
    }
    
  2. Create list of cases you want. These classes must implement condition action and ICase; Keep them light.

        public class CaseCry : IAction, ICase<int?>
        {
            public int? Key { get { return 2; } }
    
            public void Do()
            {
                Sense.Cry cry = new Sense.Cry();
                cry.Act();
            }
        }
    
    • ICase just holds a Key that it is used by Switch() function to navigate the cases.

      public interface ICase<TCase>
      {
          TCase Key { get; }
      }
      
  3. Create a Condition class that it Inherites SwitchCase generic abstract class.

    • Add all cases witch you want to Cases property.
    • Define a Switch() function and navigate Cases property to find matches cases, then execute them as a condition action.

      public class ConditionSense : SwitchCase<int?>
      {
          public ConditionSense()
          {
              Cases = new List<ICase<int?>>
              {
                  new CaseSmile(),
                  new CaseCry()
              };
      
              DefaultCases = new List<ICase<int?>> {
                  new CaseNoSense()
              };
          }
      
          public void Switch(int? key)
          {
              IEnumerable<IAction> matches = Cases.Where(p => p.Key.Equals(key))
                  .Select(p => p as IAction);
              if (matches.Count() > 0)
                  foreach (IAction match in matches)
                      match.Do();
              else
                  foreach (IAction defaultCase in DefaultCases)
                      defaultCase.Do();
          }
      }
      

Smile, Cry..., can be huge, don't worry about size of them; condition action and ICase keep them lazy load.

        public class Sense
        {
            public class Smile
            {
                public void Act()
                {
                    Console.WriteLine("I'm smiling :-)");
                }
            }

            public class Cry
            {
                public void Act()
                {
                    Console.WriteLine("I'm crying :-(");
                }
            }

            public class NoSense
            {
                public void Act()
                {
                    Console.WriteLine("I've no sense :-|");
                }
            }
        }


来源:https://stackoverflow.com/questions/410532/whats-the-best-alternative-to-an-out-of-control-switch-statement