6

我正在在线预订网站(航空公司)上工作,我想根据某些设置验证用户/客户选择的路线是否有效。现有代码使用了很多枚举,我发现自己做了很多 if/if else/else 来将特定枚举映射到我想要发生的特定操作。What I want to do is to write a enum-specific method that would do the mapping for me. Is there any standard way to do this?

这是应用程序代码的简化版本,使用来自真实应用程序的相同类名/枚举值等:

// real app has 9 members, shortened for simplicity's sake
public enum RegionType
{
    Station,
    Country,
    All
}

public enum Directionality
{
    Between,
    From,
    To
}

// simplified version
public class Flight
{
     public RegionType RegionType { get; set; }
     public RegionType TravelRegionType { get; set; }
     public string RegionCode { get; set; }
     public string TravelRegionCode { get; set; }
     public string RegionCountryCode { get; set; }
     public string TravelRegionCountryCode { get; set; }
     public Directionality Directionality { get; set; }
}

以下是一些示例用法:

    // valid flight
    Flight flight = new Flight()
    {
        RegionCode = "NY",
        CountryCode = "JP",
        RegionType = RegionType.Station,
        TravelRegionType = RegionType.Country,
        Directionality = Directionality.Between
    };

    // these are the station code/country code that user selected
    // needs to be validated against the Flight object above
    var userSelectedRoutes = new List<KeyValuePair<string, string>>() 
    { 
        new KeyValuePair<string, string>("NY", "JP"),
        new KeyValuePair<string, string>("NY", "AU"),
        new KeyValuePair<string, string>("JP", "NY")
    };

我写了一些代码验证来减少嵌套的 if/else if/else 枚举匹配:

private bool IsRouteValid(Directionality direction, string origin, 
                          string destination, string departure, string arrival)
{
    // both departure station and arrival station
    if (direction == Directionality.Between)
    {
        return (origin.Equals(departure, StringComparison.OrdinalIgnoreCase) 
          && destination.Equals(arrival, StringComparison.OrdinalIgnoreCase)
               || origin.Equals(arrival, StringComparison.OrdinalIgnoreCase) 
          && destination.Equals(departure, StringComparison.OrdinalIgnoreCase));
    }
    else if (direction == Directionality.From)
    {
            return (origin.Equals(departure, 
                    StringComparison.OrdinalIgnoreCase));   
    }
    else if (direction == Directionality.To)
    {
            return (destination.Equals(arrival, 
                    StringComparison.OrdinalIgnoreCase));
    }

    return false;
}

这是我要更改的凌乱代码:

if (flight.RegionType == RegionType.Station 
    && flight.TravelRegionType == RegionType.Country)
{
     return userSelectedRoutes.Any(route => 
            IsRouteValid(flight.Directionality, route.Key, route.Value,
            flight.RegionCode, flight.TravelRegionCode));
}
else if (flight.RegionType == RegionType.Country 
&& flight.TravelRegionType == RegionType.Station)
{
    return userSelectedRoutes.Any(route => 
           IsRouteValid(flight.Directionality, route.Key, route.Value,
           flight.CountryCode, flight.RegionCode));
}
else if (flight.RegionType == RegionType.Station 
&& flight.TravelRegionType == RegionType.Station)
{
    return userSelectedRoutes.Any(route => 
           IsRouteValid(flight.Directionality, route.Key, route.Value,
                       flight.RegionCode, flight.TravelRegionCode));
}
else if (flight.RegionType == RegionType.Station 
&& flight.TravelRegionType == RegionType.All)
{
    return userSelectedRoutes.Any(route => 
           IsRouteValid(flight.Directionality, route.Key, route.Value,
                       flight.RegionCode, route.Value));
}
else if (flight.RegionType == RegionType.All 
&& flight.TravelRegionType == RegionType.Station)
{
    return userSelectedRoutes.Any(route =>
           IsRouteValid(flight.Directionality, route.Key, route.Value,
           route.Key, flight.TravelRegionCode));
}
else if (flight.RegionType == RegionType.All 
        && flight.TravelRegionType == RegionType.All)
{
    return true;
}
else
{
    return false;
} 

传奇:

RegionCode= 出发站/出发地
TravelRegionCode= 到达站/目的地
Between= 路线必须仅来自给定的出发站和到达站,反之亦然(例如 NY-JP 或 JP-NY)
From= 从特定车站到任何路线(例如 AU - All
To= 到特定车站的任何路线(例如All -AU)

如果您注意到,上述.Any所有条件都相同,略有变化。如果可能的话,我想减少代码冗余。我使用过KeyValuePair,所以我在单一数据类型上同时拥有出发站和到达站。

关于如何使此代码不那么混乱/美观的任何想法?我知道我也硬编码IsRouteValid(),但我 100% 确定Directionality只能有 3 种可能的组合。RegionType另一方面,可以有几种组合,例如 Station-Station、Station-Country、Country-Station、Country-Country 等。

预期输出:

第一条路线有效/真 (NY-JP)
第二条路线无效/错误 (NY-AU)
第三条路线有效/真 (JP-NY) [因为DirectionalityBetween]

感谢您阅读这个很长的查询,并提前感谢您的反馈和建议。

类似帖子:

枚举和字典

4

2 回答 2

20

处理此类枚举-动作映射的一种方法是使用字典。这是一个例子:

public enum MyEnum
{
    EnumValue1,
    EnumValue2,
    EnumValue3
}

private IDictionary<MyEnum, Action> Mapping = new Dictionary<MyEnum, Action>
    {
        { MyEnum.EnumValue1, () => { /* Action 1 */ } },
        { MyEnum.EnumValue2, () => { /* Action 2 */ } },
        { MyEnum.EnumValue3, () => { /* Action 3 */ } }
    };

public void HandleEnumValue(MyEnum enumValue)
{
    if (Mapping.ContainsKey(enumValue))
    {
        Mapping[enumValue]();
    }
}

当然你也可以使用Func代替Action来处理参数。

编辑:

由于您不仅使用一个枚举,而且还使用一对枚举,因此您必须调整上面的示例以处理Tuple聚合枚举值的一种或另一种方式。

于 2013-01-18T08:57:44.630 回答
4

遵循@MatthiasG 的建议,这是我最终编写的代码:

private List<KeyValuePair<RegionType, string>> 
                GetRegionTypeAndValueMapping(Flight flight, 
                                             RegionType regionType,
                                             RegionType travelRegionType)
{
    var mapping = new List<KeyValuePair<RegionType, string>>();
    if(regionType == RegionType.Station)
    {
        mapping.Add(new KeyValuePair<RegionType, string>
                          (RegionType.Station, flight.RegionCode));
    }
    else if(regionType == RegionType.Country)
    {
        mapping.Add(new KeyValuePair<RegionType, string>
                          (RegionType.Country, flight.RegionCountryCode));
    }
    else if(regionType == RegionType.All)
    {
        mapping.Add(new KeyValuePair<RegionType, string>
                          (RegionType.All, null));
    }

    if(travelRegionType == RegionType.Station)
    {
        mapping.Add(new KeyValuePair<RegionType, string>
                          (RegionType.Station, flight.TravelRegionCode));
    }
    else if(travelRegionType == RegionType.Country)
    {
        mapping.Add(new KeyValuePair<RegionType, string>
                          (RegionType.Country, 
                           flight.TravelRegionCountryCode));
    }
    else if(travelRegionType == RegionType.All)
    {
        mapping.Add(new KeyValuePair<RegionType, string>
                          (RegionType.All, null));
    }

    return mapping;
}

我使用List<KeyValuePair<RegionType, string>>是因为Dictionary不允许密钥重复。我的钥匙是 的,RegionType enum而且有时出发站和到达站的钥匙都是一样的RegionType enum。(即 Station-Station、Country-Country 和 All-All)。

// Copyright (c) 2010 Alex Regueiro
// Licensed under MIT license, available at 
// <http://www.opensource.org/licenses/mit-license.php>.
// Published originally at 
// <http://blog.noldorin.com/2010/05/combinatorics-in-csharp/>.
// Version 1.0, released 22nd May 2010.

// modified by moi to be a generator 
public static IEnumerable<T[]> GetPermutations<T>(IList<T> list, 
                                                  int? resultSize, 
                                                  bool withRepetition)
{
    if (list == null)
    {
        throw new ArgumentNullException("Source list is null.");
    }

    if (resultSize.HasValue && resultSize.Value <= 0)
    {
        throw new ArgumentException("Result size must be any 
                                     number greater than zero.");
    }

    var result = new T[resultSize.HasValue ? resultSize.Value : list.Count];
    var indices = new int[result.Length];
    for (int i = 0; i < indices.Length; i++)
    {
        indices[i] = withRepetition ? -1 : i - 1;
    }

    int curIndex = 0;
    while (curIndex != -1)
    {
        indices[curIndex]++;
        if (indices[curIndex] == list.Count)
        {
            indices[curIndex] = withRepetition ? -1 : curIndex - 1;
            curIndex--;
        }
        else
        {
            result[curIndex] = list[indices[curIndex]];
            if (curIndex < indices.Length - 1)
            {
                curIndex++;
            }
            else
            {
                yield return result;
            }

        }
    }
}

好吧,我作弊了。:PI 需要一种计算重复排列的方法,所以我没有写一个,而是用谷歌搜索。(呵呵)我使用排列的原因是为了避免对所有可能的组合进行硬编码RegionType。我将 Alex Regueiro 的方法修改为生成器,以便我可以使用Linq它。有关排列和组合的复习,请参阅这篇非常出色的数学 stackexchange 帖子

我修改IsRouteValid()为处理RegionType.All其值为null.

这是修改后的版本:

private bool IsRouteValid(Directionality direction, string origin, 
                          string destination, string departure, 
                          string arrival)
{
     // ** == All stations/countries
     if ((origin == null && departure == "**") &&
         (destination == null && arrival == "**"))
     {
         return true;
     }
     else if (origin == null && departure == "**")
     {
        return destination.Equals(arrival, StringComparison.OrdinalIgnoreCase);
     }
     else if (destination == null && arrival == "**")
     { 
       return origin.Equals(departure, StringComparison.OrdinalIgnoreCase);
     }

    // both departure station and arrival station
    if (direction == Directionality.Between)
    {
            return (origin.Equals(departure,
                    StringComparison.OrdinalIgnoreCase) && 
                    destination.Equals(arrival, 
                    StringComparison.OrdinalIgnoreCase) || 
                    origin.Equals(arrival, 
                    StringComparison.OrdinalIgnoreCase) &&         
                    destination.Equals(departure,
                    StringComparison.OrdinalIgnoreCase));
    }
    else if (direction == Directionality.From)
    {
        return (origin.Equals(arrival, StringComparison.OrdinalIgnoreCase));
    }
    else if (direction == Directionality.To)
    {
        return (destination.Equals(departure, 
               StringComparison.OrdinalIgnoreCase));
    }

    return false;
}

好戏开始!

RegionType[] allRegionTypes = (RegionType[]) 
                              Enum.GetValues(typeof(RegionType));

var validFlights = 
      GetPermutations<RegionType>(allRegionTypes, 2, true)
         .Select(perm => new 
                         { 
                           RegionType = perm.First(),
                           TravelRegionType = perm.Last() 
                         })
         .Where(result => result.RegionType == flight.RegionType &&  
                          result.TravelRegionType ==                            
                          flight.TravelRegionType)
         .Select(map => 
                   GetRegionTypeAndValueMapping(flight,
                     map.RegionType, 
                     map.TravelRegionType));

    // same functionality as my previous messy code
    // validates all flights selected by user
    // it doesn't matter if not all flights are valid
    // as long as one of them is
    foreach(var validFlight in validFlights)
    {
        userSelectedRoutes.Any(kvp => IsRouteValid(flight.Directionality, 
                                                   kvp.Key, 
                                                   kvp.Value, 
                                                   validFlight.First().Value,
                                                   validFlight.Last().Value))
                                      .Dump("Any Flight");
    }

我创建了这段代码来演示我是如何得到与上面的预期结果相同的结果。

    foreach(var route in userSelectedRoutes)
    {
        foreach(var validFlight in validFlights)
        {
            bool condition = IsRouteValid(flight.Directionality, 
                                          route.Key, 
                                          route.Value, 
                                          validFlight.First().Value, 
                                          validFlight.Last().Value);

            Console.WriteLine(string.Format("{0}-{1} {2}", 
                                               route.Key,     
                                               route.Value, 
                                               condition.ToString()));
        }
    }

结果:

预期结果截图

笔记:

.Dump()是一个Linqpad扩展。

于 2013-01-19T03:17:10.100 回答