【问题标题】:Replacing if else statement with any design pattern or better approach用任何设计模式或更好的方法替换 if else 语句
【发布时间】:2019-01-30 15:33:41
【问题描述】:

这段代码看起来不干净,如果条件可以增长

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;
    }

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

【问题讨论】:

  • 将所有内容放入关联数组中,并使用循环。这让你只需要一个if 声明。
  • 在 C# 8 中,这可能是带有 when 子句的 switch 表达式...
  • 澄清一下,这是计算全年访问次数还是一个月访问次数?

标签: c# oop design-patterns


【解决方案1】:

应该更适合重用:你可以像这样用“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】:

    可能在 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;
        }
    }
    

    【讨论】:

    • C# 8 的一个很酷的特性,希望它已经发布了。
    • @Greg 它在 VS 16 预览版 2 中工作 - 你需要使用 &lt;LangVer&gt;8.0&lt;/LangVer&gt;,不过 - latest 还不够;它也可以在 UI 中使用,如“C# 8.0 (beta)”
    • 可能需要检查一下,我也想使用新的 null 强制执行。除非指定,否则没有值默认为 null。
    • @user6392608 那么你将不得不使用更丑的东西:)
    【解决方案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;
        }
    }
    

    【讨论】:

      【解决方案4】:

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

      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;
      

      【讨论】:

      • 那个看起来令人信服,但实际上效率很低;每次都需要做一个OrderBy 等是非常昂贵的,而且你从来没有使用过Dictionary&lt;int,int&gt; 的主要功能 - 它根本不会给你买任何东西 - 你会最好使用平面数组和只是 First
      • 具体来说,类似public int VisitMonth(int months) =&gt; monthsToVisits.First(x =&gt; months &lt;= x.Months).Visit; where static readonly (int Months,int Visit)[] monthsToVisits = new (int,int)[] { (1,1), (2,2), (4,3), (6,4) };
      【解决方案5】:

      这仅在返回值 (visit) 始终为每个可用条件线性增加时才有效(即,visit 在每个 if/else if 块中增加 1)。

      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;
      }
      

      这种方法的好处是实际上不必定义返回值 (visit) 是什么。从某种意义上说,这使得它具有可扩展性,如果在中间某处有新限制(例如 22)出现需求,您不必为每个后续条件重新映射 visit 值,因为它只是根据其在数组中的位置派生。


      这是一个工作示例:

      static void Main(string[] args)
      {
          Console.WriteLine($"0: {VisitMonth(0)}");
          Console.WriteLine($"5: {VisitMonth(5)}");
          Console.WriteLine($"60: {VisitMonth(60)}");
          Console.WriteLine($"100: {VisitMonth(100)}");
          Console.ReadLine();
      }
      

      【讨论】:

        【解决方案6】:

        执行此操作的一种方法是将要比较的值存储在List&lt;int?&gt; 中,然后返回满足条件months &lt;= item 的第一个项目的index + 1 或列表Count + 1如果没有一个符合该条件。

        如果没有匹配项,我们使用int? 允许我们在调用FirstOrDefault 时获得null 结果(我们不使用int,因为default(int) == 0 所以我们不知道是否我们匹配了索引0 处的第一项,或者没有匹配项并且返回默认值0)。这样,我们可以测试 null 结果(表示不匹配),并在这种情况下返回 List.Count + 1

        这将您的方法减少到 2 行代码,并且添加像 if (months &lt;= 120) 这样的新条件就像在 values 赋值中添加 120 一样简单:

        public static int Visit(int months)
        {
            var values = new List<int?> {1, 2, 4, 6, 9, 12, 15, 18, 24, 30, 36, 48, 60};
        
            return (values.Select((v, i) => new {value = v, index = i})
                .FirstOrDefault(i => months <= i.value)?.index ?? values.Count) + 1;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-03-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-23
          • 1970-01-01
          • 2014-11-29
          • 1970-01-01
          相关资源
          最近更新 更多