簡體   English   中英

C#替換了巨大的if-else語句

[英]C# replacing huge if-else statement

假設我有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方法來解決我應該稍后調用的方法。

在此輸入圖像描述

也許是否有任何其他方法來替換if-else語句更可讀的代碼,設計模式或更高效的東西?

我想將整個可能的狀態組合為標記枚舉值,但不確定我是否可以稍后從對象屬性創建枚舉值。

聽起來你想要一些規則集來檢查項目。 我認為使這個更具可讀性的最簡單的形式是將項目,規則的屬性和操作傳遞給單獨的方法:

    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次。

你可以抽象送人有點進一步作出這樣的規則單獨的對象,你可以把一個列表或字典,但像這樣的定義,它不使其成為更具可讀性,但它確實增加了一些可驗證性的好處,以及添加不同類型規則的可能性,而不會再次弄亂您的清潔列表。

我認為你最好的選擇是停止一個將你的值映射到方法的Dictionary
現在有幾個選項可能是你詞典中的關鍵 - 見下文。

免責聲明請注意,if if語句只是一個問題,如果它在代碼庫中傳播/復制,並且頻繁更改 - 將內容放入字典並不能真正降低復雜性 在字典中使用方法也會改變代碼的語義。 第一個問題應該是 - 我是否在運行時更改映射? 它們真的應該是動態的嗎?

  1. 使用鍵值ValueTuple結構的Dictionary可以使用語法(Shape, Color, Material) - 這是最簡單的一種。 請注意 - 不是 Tuple類,而是ValueTuple結構。
  2. Dictionary與關鍵Item類本身,但那么你需要在Item 注意正確的相等比較 您可以使Item成為一個免費獲取的結構 (但性能較慢,來自System.ValueType中通常情況下使用反射的相等比較),或者將其保留為類(或結構)並實現IEquatable<Item>EqualsGetHashCode
    如果沒有適當的相等比較,您的字典查找將無法工作(由@ckuri建議)
  3. 使用不帶字典的ValueTuple來簡化您的代碼。
  4. 狀態模式的 變體 ,其中包含處理程序的共享接口/基類。 Handler是一個單獨的類,它包含一組特定值的操作,如AnyBlueGlassHandler : Handler 每個處理程序檢查If條件,當為true時,運行操作。 然后,您可以將處理程序放在List<T> ,並將它們應用於像handlers.Foreach(x=>x.Handle(item))

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)) { }

嘗試使用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();  
}
void RunAction((Shape shape, Color color, Material material) item)
{
    switch(item)
    {
        case var i1 when i1.color == Color.red && i1.shape == Shape.square:
        case var i2 when i2.color == Color.blue:
            // Do action...
            break;
        case var i1 when i1.shape == Shape.trangle && i1.material == Material.metal:
            // Do action...
            break;
        default:
            // Do action...
            break;
    }
}

此解決方案使用Value Tuples ,但主要在switch語句中使用C#7 模式匹配

我不認為它完全解決了你的問題干凈,但我認為多行案例給你提供了超過if語句方法的額外可讀性,並且很容易實現。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM