C#替换巨大的if-else语句

时间:2018-09-19 19:30:46

标签: c# design-patterns

让我说我有Item对象的女巫大多拥有这样的枚举属性

public enum Shape
{
    square = 1,
    trangle = 2,
    any = 3
}

public enum Color
{
    blue = 1,
    red = 2,
    yellow = 3,
    green = 4
}


public enum Material
{
    glass = 1,
    wood = 2,
    metal = 3
}


public class Item
{
    public Shape ItemShape { get; set; }
    public Color ItemColor { get; set; }
    public Material ItemMaterial { get; set; }
}

我想做的是取决于全部三个属性的组合,我以后需要做一些动作;

我当时正在考虑使用if-else组合,例如:

if(item.ItemShape == Shape.square && item.ItemColor == Color.blue && item.ItemMaterial == Material.glass)
        {
            //call Action1
        }
        else if(item.ItemShape == Shape.square && item.ItemColor == Color.blue && item.ItemMaterial == Material.wood)
        {
            // action2
        }
        ......

问题是我大约有16种组合,因此if-else方法将无法解决以后应该调用的方法。

enter image description here

也许还有其他方法可以代替if-else语句,使代码更具可读性,设计模式或更有效吗?

我当时正在考虑将所有可能的状态组合为标志枚举值,但不确定是否以后可以从object属性创建枚举值。

4 个答案:

答案 0 :(得分:2)

听起来像您想要一些规则集来检查一项。 我认为使此内容更具可读性的最简单形式是将项目,规则的属性和操作传递给单独的方法:

    public bool RunActionIf(Item item, Shape shape, Color color, Material material, Action action)
    {
        if(item.ItemShape == shape && item.ItemColor == color && item.ItemMaterial == material)
        {
            action();
            return true;
        }

        return false;
    }

    public void RunAction(Item item)
    {
        var result =
            RunActionIf(item, Shape.square, Color.blue, Material.glass, Action1) ||
            RunActionIf(item, Shape.square, Color.blue, Material.wood, Action2) ||
            /* Implement your whole table like this */;

        if(!result)
        {
            throw new ArgumentException("No matching rule found", nameof(item));
        }
    }

该方法的主要优点是它更短并且声明中的“开销”更少。您可以轻松地看到:形状X +颜色Y +材质Z =该动作。

另一个优点是,实现某些异常更加容易,例如,允许规则参数之一为null来指示任何颜色,或者使用Color.any来支持该例外,尽管我认为这令人困惑在枚举中使用any和其他颜色...无论如何,我离题了。关键是,如果要实现该目标,则只需在一个地方执行一次,而不必复制16次。

您可以通过将这样的规则设置为一个单独的对象来进一步抽象该对象,您可以将其放置在列表或字典中,但是对于这样的定义,它并不能使其变得那个更具可读性,尽管它确实增加了可测试性的一些好处,以及添加各种规则的可能性,而又不会弄乱您的清除列表。

答案 1 :(得分:2)

我认为您最好的选择是创建一个Dictionary,它将您的值映射到方法。
现在有几个选项可能是字典中的关键-参见下文。

免责声明 还请注意,如果if语句在整个代码库中分散/重复并且经常更改,那么if语句只是一个问题,将其放入字典并不会真正降低复杂度。在字典中包含方法也会更改代码的语义。第一个问题应该是-我是否在运行时更改了映射?它们真的应该是动态的吗?

  1. Dictionary具有键ValueTuple结构,您可以使用语法(Shape, Color, Material)-这是最简单的语法。 请注意-不是 Tuple类,而是ValueTuple结构。
  2. Dictionary和键Item类本身,但是您需要注意Item中的正确相等比较。您可以将Item做成结构以免费获得它(但性能较慢,这是由于System.ValueType中的等式比较(一般情况下使用反射)所致),也可以保留它作为类(或结构)并实现IEquatable<Item>EqualsGetHashCode
    没有适当的相等比较,您的字典查找将无法正常工作(如@ckuri所述)
  3. 使用ValueTuple而不使用字典来简单地压缩您的代码。
  4. State pattern变体,其中您的处理程序具有共享的接口/基类。处理程序是一个单独的类,其中包含针对一组特定值(例如AnyBlueGlassHandler : Handler)的一个操作。每个处理程序都会检查If条件,如果为true,则运行操作。然后,您可以将处理程序放入List<T>中,并将其应用于handlers.Foreach(x=>x.Handle(item))
  5. 之类的项目。

Item为键的代码如下:

    public static class ItemExtensions
    {
    static Dictionary<Item, Action<Item>>
        methods = new Dictionary<Item, Action<Item>>()
        {
            { new Item(Shape.any, Color.blue, Material.glass), x=> { /*do something*/ } }
        };


    public static bool TryApply(this Item item)
    {
        if (methods.TryGetValue(item, out var action))
        {
            action(item);
            return true;
        }
        return false;
    }
}

ValueTuple为键的代码可能类似于

 public static class ItemExtensionsUsingValueTuple
 {
    static Dictionary<(Shape, Color, Material), Action<Item>>
        methods = new Dictionary<(Shape, Color, Material), Action<Item>>()
        {
            { (Shape.any, Color.blue, Material.glass), x=> { /*do something*/ } }
        };


    public static bool TryApply(this Item item)
    {
        if (methods.TryGetValue((item.ItemShape, item.ItemColor, item.ItemMaterial), out var action))
        {
            action(item);
            return true;
        }
        return false;
    }
}

带有ifs的代码的更精简版本可能看起来像:

  • 在Item类上声明Key属性
    public (Shape, Color, Material) Key => (ItemShape, ItemColor, ItemMaterial);
  • 使用更优雅的if语句
    if ( item.Key == (Shape.any, Color.blue, Material.glass)) { }

答案 2 :(得分:1)

例如,尝试Dictionary<T>

static Dictionary<Tuple<Shape, Color, Material>, Action> s_Actions = new
  Dictionary<Tuple<Shape, Color, Material>, Action>() {
    {Tuple.Create(Shape.square, Color.blue, Material.glass), () => { ... } },
     ...  
    {Tuple.Create(Shape.any, Color.red, Material.metal), () => { ... } },
     ...
  };

private static void RunAction(MyItem item) {
  Action action;

  // Either exact match or match with Any 
  if (!s_Actions.TryGetValue(Tuple.Create(item.ItemShape, item.ItemColor, item.ItemMaterial), 
                             out action))
    action = s_Actions.FirstOrDefault(pair => pair.Key.Item1 == Color.any &&
                                              pair.Key.Item2 == item.ItemColor &&
                                              pair.Key.Item3 == item.ItemMaterial) 

  // Execute action if it's found
  if (action != null) 
    action();  
}

答案 3 :(得分:0)

df = pd.read_excel("file.xlsx", index_col= None, na_values=['NA'] , usecols=[18,4,5,21,0,1])
df2 = df[(df.TIME >= x) , (df.TIME <= y)]
df3 = df2[['a','b','c']]

此解决方案使用Value Tuples,但在switch语句中主要使用C#7 pattern matching

我认为它不能完全彻底地解决您的问题,但我认为多行案例使您比if语句方法更具可读性,并且易于实现。