【问题标题】:Best approach to programming highly complex business/math rules编写高度复杂的业务/数学规则的最佳方法
【发布时间】:2010-06-29 18:26:42
【问题描述】:

我必须获取一段数据,并对其应用大量可能的变量。我真的不喜欢使用大量 if 语句的想法,因此我正在寻求帮助以简化并使其更易于维护。

举个例子:

if (isSoccer)
    val = soccerBaseVal;
else if (isFootball)
    val = footballBaseVal;
.... // 20 different sports

if (isMale)
   val += 1;
else
    val += 5;

switch(dayOfWeek)
{
    case DayOfWeek.Monday:
       val += 12;
    ...
}

等等.. 等等.. 等等.. 可能有 100-200 种不同的测试和公式变化。

这似乎是一场维护噩梦。有什么建议吗?

编辑:

为了进一步增加问题,许多变量仅在某些情况下使用,因此它不仅仅是一组具有不同值的固定逻辑。逻辑本身必须根据条件进行更改,可能是从以前的变量应用的条件(例如,如果 val > threshold)。

所以是的,我同意对许多值使用查找,但我还必须有可变逻辑。

【问题讨论】:

标签: c# math business-logic


【解决方案1】:

避免大型交换结构的常用方法是将信息放入数据结构中。创建一个枚举SportType 和一个包含相关值的Dictionary<SportType, Int32>。你可以简单地写val += sportTypeScoreMap[sportType]就完成了。

这种模式的变体将在许多类似情况下为您提供帮助。

public enum SportType
{
    Soccer, Football, ...
}

public sealed class Foo
{
    private static readonly IDictionary<SportType, Int32> sportTypeScoreMap =
        new Dictionary<SportType, Int32>
        {
            { Soccer, 30 },
            { Football, 20 },
            ...
        }

    private static readonly IDictionary<DayOfWeek, Int32> dayOfWeekScoreMap =
        new Dictionary<DayOfWeek, Int32>
        {
            { DayOfWeek.Monday, 12 },
            { DayOfWeek.Tuesday, 20 },
            ...
        }

    public Int32 GetScore(SportType sportType, DayOfWeek dayOfWeek)
    {
        return Foo.sportTypeScoreMap[sportType]
             + Foo.dayOfWeekScoreMap[dayOfWeek];
    }
}

【讨论】:

  • 不错。我可能会使用这样的东西。但是,我已经将问题扩大了一点。查看我的编辑。
  • 您将始终达到无法(或有用)在常用方法中提取更多功能的地步 - 理想情况下,您必须执行的所有操作都是不同的并且没有剩余的通用模式.这种不可压缩的剩余(与数据压缩的类比非常适合 - 尝试提取所有常见模式并且所有剩余看起来像无结构的随机噪声)仍然可能非常复杂。剩下的最好的办法就是用几个名字好听的小方法尽可能好地模块化它。
  • 正是 Java 强大的枚举类的用例——你可以将额外的数据与枚举值本身关联起来,从而不需要所有这些字典 (例如,允许您将 GetScore() 放在枚举类中,这样您就可以调用 sportType.GetScore(dayOfWeek))。 Java 做对而 C# 做错的少数几件事之一......
  • 另见:here。它足够复杂,只有当你需要很多这样的字典时它才真正有用。..
【解决方案2】:

使用 switch 语句或过滤函数。

通过过滤器功能,我的意思是:

func filter(var object, var value)
{
    if(object == value)
        object = valueDictionary['value'];
}

然后应用过滤器:

filter(theObject, soccer)
filter(theObject, football)

请注意,过滤器使用字典效果会更好,但这不是必需的。

【讨论】:

    【解决方案3】:

    来自 The Pragmatic Programmer 的 Cribing,您可以使用 DSL 来封装规则并编写流程引擎。对于您提出的问题,解决方案可能如下所示:

    MATCH{
        Soccer   soccerBaseVal
    
        IsMale   5
        !IsMale  1
    }
    
    SWITCH{
        Monday   12
        Tuesday  13
    }
    

    然后匹配 MATCH 的第一列中的所有内容,以及您来到的每个 SWITCH 中的第一项。您可以制作任何您喜欢的语法,然后只需编写一些脚本将其塞进代码中(或者使用 Xtext,因为它看起来很酷)。

    【讨论】:

      【解决方案4】:

      这里有一些想法:

      1 使用查找表:

      var val = 0;
      
      SportType sportType = GetSportType();
      
      val += sportvalues[sportType];
      

      您可以从数据库中加载表。

      2 使用工厂模式:

      var val = 0;
      
      val += SportFactory.Create(sportType).CalculateValue();
      

      Dynamic Factory Pattern 在经常向代码中添加新(运动)类型的情况下很有用。此模式使用反射来防止工厂类(或任何全局配置)被更改。它允许您简单地向代码中添加一个新类。

      当然,在您的情况下,使用动态工厂甚至工厂都可能是矫枉过正。你是唯一能说出来的人。

      【讨论】:

        【解决方案5】:

        作为第一步,我可能会将每个逻辑处理区域分解为自己的方法:(可能不是第一遍的最佳名称)

        EnforceSportRules
        ProcessSportDetails
        EnforceGenderRules 
        

        接下来,根据规则的复杂程度,我可能会将每个部分分成自己的类,并让它们由主类(如工厂)处理。

        GenderRules
        GenderContext
        

        【讨论】:

          【解决方案6】:

          我没有什么特别要给你的,就是首先建议不要把它当作一个大块——把它分成几个部分,在重要部分之间设置评论分隔符。

          另一个建议是,如果您要像示例中那样进行许多非常短的测试,请打破常规,将 val 增量器与评估和缩进放在同一行,以便它们彼此对齐。

          if (isSoccer)              val = soccerBaseVal;  
          if (isMale)                val += 1; 
          else                       val += 5; 
          
          switch(dayOfWeek){ 
              case DayOfWeek.Monday: val += 12; 
              ... 
          }  
          

          过多的空白会使这数百个内容变成数百行,使垂直滚动过多且难以获得事物的整体视图。

          【讨论】:

            【解决方案7】:

            如果您真的只是在这种类型中添加值,我会创建一个具有已定义索引的枚举,这些索引对应于数组中的存储值。然后你可以这样做:

            enum Sport
            {
                football = 0,
                soccer   = 1,
                //...
            }
            
            int sportValues[] = { 
                /* footballValue */,
                /* soccerValue */,
                /* ...Values */
            };
            
            int ApplyRules(Sport sport, /* other params */)
            {
                int value = startingValue;
                value += sportValues[(int)sport];
                value += /* other rules in same fashion */;
            }
            

            【讨论】:

              【解决方案8】:

              考虑实现Strategy Pattern,它利用继承/多态性来使管理单个函数更加合理。通过将每个函数分离到其自己的专用类中,您可以摆脱拥有长达数英里的 case 块或 if 语句的噩梦。​​

              不确定 C# 是否支持它(或永远支持),但 VB.NET 将 XML 注释 CompletionList 指令集成到智能感知中,当与策略模式结合使用时,它可以让您轻松使用枚举具有面向对象的开放式可扩展性。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-10-22
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多