用任何设计模式或更好的方法替换if else语句

时间:2019-01-30 15:33:41

标签: c# oop design-patterns

此代码看起来不干净,如果情况可能会变迁

public int VisitMonth(int months)
    {
        int visit = 0;

        if (months <= 1)
        {
            visit = 1;
        }
        else if (months <= 2)
        {
            visit = 2;
        }
        else if (months <= 4)
        {
            visit = 3;
        }
        else if (months <= 6)
        {
            visit = 4;
        }
        else if (months <= 9)
        {
            visit = 5;
        }
        else if (months <= 12)
        {
            visit = 6;
        }
        else if (months <= 15)
        {
            visit = 7;
        }
        else if (months <= 18)
        {
            visit = 8;
        }
        else if (months <= 24)
        {
            visit = 9;
        }
        else if (months <= 30)
        {
            visit = 10;
        }
        else if (months <= 36)
        {
            visit = 11;
        }
        else if (months <= 48)
        {
            visit = 12;
        }
        else if (months <= 60)
        {
            visit = 13;
        }
        else
        {
            visit = 14;
        }
        return visit;
    }

有没有更好的解决方案来解决这个问题?遗憾的是,该函数不是线性函数,因此以数学方式对其进行编码并不容易。

6 个答案:

答案 0 :(得分:5)

可能在C#8中(此功能尚未正式发布,但如果您将其打开,则可在最新的IDE中使用):

int months = ...;
int visit = months switch
{
    int j when j <= 1 => 1,
    int j when j <= 2 => 2,
    int j when j <= 4 => 3,
    int j when j <= 6 => 4,
    int j when j <= 9 => 5,
    // ...
    _ => 42 // default
};

由于这是一种方法,因此可以在早期的C#中执行 类似的操作

public int VisitMonth(int months)
{
    switch (months)
    {
        case int j when j <= 1: return 1;
        case int j when j <= 2: return 2;
        case int j when j <= 4: return 3;
        // etc
        default: return 14;
    }
}

答案 1 :(得分:5)

应该更适合重用:您可以使用“ inRange”方法编写“ Interval”类,如下所示:

 public struct Interval<T>
       where T : IComparable
{
    public T Start { get; set; }
    public T End { get; set; }
    public T Visit { get; set; }

    public Interval(T visit, T start, T end)
    {
        Visit = visit;
        Start = start;
        End = end;
    }

    public bool InRange(T value)
    {
      return ((!Start.HasValue || value.CompareTo(Start.Value) > 0) &&
          (!End.HasValue || End.Value.CompareTo(value) >= 0));
    }
}

然后像这样使用:

public static readonly List<Interval<int>> range = new List<Interval<int>>
        {
                new Interval<int>(1, 0, 1),
                new Interval<int>(2, 1, 2),
                new Interval<int>(3, 2, 4),
                new Interval<int>(4, 4, 6),
                new Interval<int>(5, 6, 9),
                new Interval<int>(6, 9, 12),
                new Interval<int>(7, 12, 15),
                new Interval<int>(8, 15, 18),
                new Interval<int>(9, 18, 24),
                new Interval<int>(10, 24, 30),
                new Interval<int>(11, 30, 36),
                new Interval<int>(12, 36, 48),
                new Interval<int>(13, 48, 60),
                new Interval<int>(14, 60, int.MaxValue)
        };

var months = 5;
var visit = range.Where(x => x.InRange(months)).Select(x => x.Visit).FirstOrDefault();

答案 2 :(得分:3)

void Main()
{
    var conditionsChain = new SimpleCondition(0, 1);
        conditionsChain.AddNext(new SimpleCondition(1, 1))
        .AddNext(new SimpleCondition(2, 2))
        .AddNext(new SimpleCondition(4, 3))
        .AddNext(new SimpleCondition(6, 4))
        .AddNext(new SimpleCondition(9, 5))
        .AddNext(new SimpleCondition(12, 6))
        .AddNext(new SimpleCondition(15, 7))
        .AddNext(new SimpleCondition(18, 8))
        .AddNext(new SimpleCondition(24, 9))
        .AddNext(new SimpleCondition(30, 10))
        .AddNext(new SimpleCondition(36, 11))
        .AddNext(new SimpleCondition(48, 12))
        .AddNext(new SimpleCondition(60, 13))
        .AddNext(new SimpleCondition(14));

    for (int i = 0; i < 62; i++)
    {
        Console.WriteLine($"{i}: {conditionsChain.Evaluate(i) - VisitMonth(i)}");
    }
}

class SimpleCondition
{
    private SimpleCondition _next;

    private int _key;
    private int _result;

    public SimpleCondition(int key, int result)
    {
        _key = key;
        _result = result;
    }

    public SimpleCondition(int result) : this(-1, result)
    {
    }

    public int Evaluate(int key)
    {
        if(_key == -1)
        {
            return _result; 
        }

        if(key <= _key)
        {
            return _result;
        }
        else
        {
            if(_next == null)
            {
                throw new Exception("Default condition has not been configured.");
            }
            return _next.Evaluate(key); 
        }
    }

    public SimpleCondition AddNext(SimpleCondition next)
    {
        return _next = next;
    }
}

答案 3 :(得分:2)

您可以使用字典将月份存储为键,将访问次数存储为值。

var monthsToVisits= new Dictionary<int,int>
{
    {1,1},
    {2,2},
    {4,3},
    {6,4}
};

等...

使用此功能,您可以轻松查找最大的密钥,即仅比您要检查的月份多 个以及相关的值。

int months = 42;
int visit = monthsToVisits.Where(x => x.Key > months)
                        .OrderBy(x => x.Key)
                        .First().Value;


更新

正如@Marc Gravell所说,使用字典是一种非常低效的解决方案。更好的方法是使用静态数组。

static readonly (int Months,int Visit)[] monthsToVisits = new (int,int)[] 
{ 
    (1,1), 
    (2,2), 
    (4,3), 
    (6,4) 
};

public int VisitMonth(int months) => 
    monthsToVisits.First(x => months <= x.Months).Visit;

答案 4 :(得分:0)

只有在每种可用条件下返回值(visit)总是线性增加的情况下,这才起作用(即visit在每个if / {{1}中增加1 }块)。

else if

此方法的好处是实际上不必定义返回值(static readonly int[] _monthLimits = new int[] { 1, 2, 4, 6, 9, 12, 15, 18, 24, 30, 36, 48, 60 }; public static int VisitMonth(int months) { int visit = 0; var maxMonths = _monthLimits[_monthLimits.Length - 1]; if (months <= maxMonths) { // Only iterate through month limits if the given "months" is below the max available limit for (var i = 0; i < _monthLimits.Length; i++) { if (months <= _monthLimits[i]) { visit = i + 1; break; } } } else { // The given "months" is over the max, default to the size of the array visit = _monthLimits.Length + 1; } return visit; } )是什么。从某种意义上说,这使得它可以扩展,如果某个需求出现在中间某个新限制(例如22)的地方,那么您不必为每个后续条件重新映射visit值只是根据其在数组中的位置得出的。


这是此工作的一个示例:

visit

enter image description here

答案 5 :(得分:0)

一种方法是将要比较的值存储在List<int?>中,然后返回满足条件index + 1的第一项的months <= item,或列表Count + 1(如果没有一个符合条件)。

如果没有匹配项,我们将在调用int?时使用null来获得FirstOrDefault的结果(我们不使用int,因为{{ 1}},所以我们不知道是否匹配索引default(int) == 0的第一项,或者没有匹配项,并且返回了默认值0。这样,我们可以测试0的结果(表示没有匹配项),然后返回null

这会将您的方法减少到两行代码,并且添加一个像List.Count + 1这样的新条件就像向if (months <= 120)分配中添加120一样简单:

values