用图案替换开关/外壳

时间:2009-12-01 22:05:50

标签: c# asp.net design-patterns

我在后面的代码中有三次与此示例非常相似的代码。 每次交换机切换到发送给它的选项。每 除了参数之外,案例中的代码完全相同 基于案例。是使用开关/案例和方法的最佳方式 去做这个?我是否应该考虑使用某种类型的设计模式来避免重复开关/案例结构?

string option = dropDownList.SelectedValue.ToString();
switch (option.ToUpper())
{
    case "ALPHA":
        // do repeative code method here; only change is a parameter
        break;
    case "BRAVO":
        // do repeative code method here; only change is a parameter
        break;
    case "CHARLIE":
        // do repeative code method here; only change is a parameter
        break;
    case "DELTA":
        // do repeative code method here; only change is a parameter
        break;
    default:
        break;
}

7 个答案:

答案 0 :(得分:11)

您可以构建一个表格,将string转换为parameter value

var lookup = new Dictionary<string, ParaType> ()  
{
    { "ALPHA", a  },
    { "BETA", b },
    ....
};

ParaType para;
if (lookup.TryGetValue(option, out para))   // TryGetValue, on popular request
{       
   // do repeative code method here; with para
}

答案 1 :(得分:11)

编译器非常擅长优化开关/案例结构; CLR可能会把它变成查询表或类似的快速,所以手动滚动自己的版本,如Henk Holterman建议不是我推荐的。在选择最佳算法时,CLR可以做得更好。

如果这是一个优雅或可维护性的问题,并且你有几个关于同一个类执行类似功能的交换机/案例,那么改进它的一种方法是将与单个“案例”相关的所有功能封装到其中自己的类实例,如下所示:

class MyOption
{
    public static readonly MyOption Alpha = new MyOption(1, 10, "Alpha Text");
    public static readonly MyOption Bravo = new MyOption(2, 100, "Bravo Text");
    public static readonly MyOption Charlie = new MyOption(3, 1000, "Charlie Text");
    // ... Other options ...
    public static readonly MyOption Default = new MyOption(0, 0, null);

    public MyOption(int id, int value, string text)
    {
        this.ID = id;
        this.Value = value;
        this.Text = text;
    }

    public int ID { get; private set; }
    public int Value { get; private set; }
    public string Text { get; private set; }
}

然后在你的班级/控制/页面中:

static MyOption GetOption(string optionName)
{
    switch (optionName)
    {
        case "ALPHA":
            return MyOption.Alpha;
        case "BRAVO":
            return MyOption.Bravo;
        case "CHARLIE":
            return MyOption.Charlie;
        // ... Other options ...
        default:
            return MyOption.Default;
    }
}

private MyOption GetOptionFromDropDown()
{
    string optionName = GetOptionNameFromDropDown();
    return GetOption(optionName);
}

private string GetOptionNameFromDropDown()
{
    // ... Your code ...
}

之后,您可以开始制作事件和其他方法:

private void control1_SomeEvent(object sender, EventArgs e)
{
    MyOption option = GetOptionFromDropDown();
    DoSomething(option.ID);
}

private void control2_SomeEvent(object sender, EventArgs e)
{
    MyOption option = GetOptionFromDropDown();
    DoSomethingElse(option.Value);
}

当然,如果您想要将这些开关/案例中的几个重构为一个,这只是一个有用的模式。如果您只有一个交换机/机箱,那么您最终会以这种方式获得更多更多代码,所以请不要管它!

提高可维护性的其他可能性包括:

  • 将字符串更改为枚举类型(使用Enum.Parse转换optionName);
  • 将所有MyOption / GetOption内容移动到自己的类中(如果您有多个类/控件/页面都需要在同一组选项上运行);
  • 如果您确实需要为每个方法调用不同的方法,请将方法委托添加到MyOption类;
  • 如果可能,让DropDownList或其他控件存储对MyOption实例的直接引用。

就是这样。它编写简单,易于理解,易于维护,如果您有大量的开关/案例结构,它将节省您的时间,并且它仍然允许CLR执行最佳的优化。唯一的成本是保存那些只读字段所需的少量内存。

答案 2 :(得分:3)

我想我会将switch语句移动到一个单独的函数中并让它为每种情况返回参数值:

    private static string GetParameterForAllCases(string option)
    {
        switch (option.ToUpper())
        {
            case "ALPHA":
                return "ALPHA Parameter";
            case "BRAVO":
                return "BRAVO Parameter";
            case "CHARLIE":
                return "CHARLIE Parameter";
            case "DELTA":
                return "DELTA Parameter";
            default:
                return "Default Parameter";
        }
    }

然后你可以调用你的工作方法一次:

        string option = dropDownList.SelectedValue.ToString();

        WorkMethod(GetParameterForAllCases(option);

如果您不想为默认原因执行工作方法,或者您有多个参数值,则可以更改GetParameter方法以使用输出参数:

    private static bool GetParameter(string option, out string value)
    {
        switch (option.ToUpper())
        {
            case "ALPHA":
                value = "ALPHA Parameter";
                return true;
            case "BRAVO":
                value = "BRAVO Parameter";
                return true;
            case "CHARLIE":
                value = "CHARLIE Parameter";
                return true;
            case "DELTA":
                value = "DELTA Parameter";
                return true;
            default:
                value = null;
                return false;
        }
    }

并称之为:

        string option = dropDownList.SelectedValue.ToString();

        string value;
        if (GetParameter(option, out value))
            WorkMethod(value);

答案 3 :(得分:2)

此问题的关键是使存储在dropDownList中的对象提供参数(直接或通过索引到数组中)。然后可以完全删除switch语句。

如果参数是下拉列表中显示的对象的属性,那么这将非常有效地提供该值。

如果下拉列表中的值可以为参数值数组提供数字索引,那么这将在运行时效率方面击败一系列字符串比较。

这些选项中的任何一个都比打开字符串更清晰,更短,更容易维护。

答案 4 :(得分:1)

也许从选项转换为方法参数?

这将允许您删除switch语句并简单地为方法提供转换后的参数。

答案 5 :(得分:0)

这对你正在做的事情来说可能有点过头了,但代码可以最终更清洁。

class AlphabetChar
{
    public virtual void DoSomething(){}
}

class Alpha : AlphabetChar {}
class Bravo : AlphabetChar {}
...

class AlphabetCharFactory
{
    public static AlphabetChar GetByName(string name)
    {
         switch (name.ToUpper())
         {
             case "ALPHA":
                   return new Alpha();

             ...

             default:
                  //google for "wiki null object"
                  return new NullAlphabetChar();
         }
    }
}

然后调用代码变为......

string option = dropDownList.SelectedValue.ToString();

AlphabetChar alphabetChar = AlphabetCharFactory.GetByName(option);

alphabetChar.DoSomething();

就像我说的,如果你所做的只是设置一个参数(基本上是从子类中设置基类中的属性),那就太过分了。但如果你的逻辑更复杂,这对你来说可能是一个可行的选择。

答案 6 :(得分:0)

嗯,我认为非常快速的解决方案是使用字典。字典是非常快速的结构,当你使用一个键 - &gt;价值逻辑。因此,在您的情况下,您可以使用此:

var processingValues = new Dictionary<string, Func<string, string>>() {
     {"ALPHA", yourProcessor.Alpha},
     {"BRAVO", yourProcessor.Bravo},
      ...
}

之后,您可以为ALPHA,BRAVA,......创建功能。

public class Processor
{
   public string Alpha(string data)
   {
        return "do something";
   }

   public string Bravo(string data)
   {
        return "do something";
   }
...
}

最后返回一些函数:

public string SomethingToReturn(string key, string value)
  {
       if (proccessValues.ContainsKey(name))
       {
           return proccessValues[name].Invoke(value);
       }
       return string.Empty;
   }

希望有所帮助。