【问题标题】:Code improvements for implementing business logic实现业务逻辑的代码改进
【发布时间】:2010-01-28 20:51:20
【问题描述】:

我之前在 SO 上问过 this 问题。这与它有关。我们有类似这样的代码库:

IRecipie FindRecipiesYouCanMake(IEnumerable<Ingredientes> stuff, Cook cook)
{
 if(stuff.Any(s=>s.Eggs && s.Flour) && cook.DinerCook)
 {
  if(s=>s.Sugar)
   return new Pancake("Yum");
  if(s=>s.Salt)
   return new Omlette("Yay");
 }
 /*.....
 ......
 .....
 loads of ifs and buts and else*/
}

我想摆脱这种混乱并采取更多的数据结构和OO路线。甚至我提供的代码示例也没有现在那么可怕。我查看了规范模式,发现它适用。关于如何改进代码的任何想法。

编辑:现在我意识到了,我什至可能想实现这个签名的方法:

List<IRecipe> WhatAllCanBeCooked(IEnumerable<Ingredients> stuff, Cook cook);

【问题讨论】:

  • 我正准备将此标记为垃圾邮件 :)
  • @Mehrdad:呃!需要有一种方法来否决 cmets ;)
  • 不要在你的煎蛋卷里放面粉 - yuk..,你能提供更多关于你想要完成什么的信息吗?
  • 我知道 omlettes 没有面粉……但这就是我试图暴露的混淆逻辑的危险……我在示例中做得不好……但希望你明白我的意思: P

标签: c# business-logic business-rules


【解决方案1】:

我会将此逻辑委托给各个 IRecipie 类:

if (Pancake.CanBeMadeBy(stuff, cook)) {
    return new Pancake("Yum");
}
....


public class Pancake: IRecipe {
    ...
    public static bool CanBeMadeBy(IEnumerable<Ingredientes> stuff, Cook cook) {
        return stuff.Any(s=>s.Eggs && s.Flour && s.Sugar) && cook.DinerCook;
    }

}

根据评论进行编辑

要找到所有可以烹饪的食谱,只需执行以下操作:

List<IRecipe> results = new List<IRecipe>();

if (Pancake.CanBeMadeBy(stuff, cook)) {
    results.Add(new Pancake("Yum");
}
....

编辑 2 或者,如果您将所有可能的配方列表存储在某处,您可以将 CanBeMadeBy 转换为实例方法而不是静态方法,然后执行以下操作:

List<IRecipe> allRecipes = // all possible recipes
...
return allRecipes.Where(r => r.CanBeMadeBy(stuff, cook));

【讨论】:

  • 我认为观点有所不同,我不是想知道是否可以制作煎饼,而是想看看用这个项目和这个厨师可以创建什么所有的食谱。
【解决方案2】:

一些想法:

  • 使用decision tables

  • 使用strategy pattern。这有助于您封装一组属于不同具体类的动作或参数。一旦您决定使用哪种策略,您就不再需要任何“ifs”来在策略之间分派。

编辑:一些额外的想法:

  • 从“小”开始:大多数情况下,只需简单地重构为更小、命名良好、可重用的函数即可帮助您减少 if-else-if-else-soup。有时,一个简单的、命名良好的布尔变量就可以解决问题。两者都是重构示例,您可以在 Fowler's book "Refactoring" 中找到。

  • 想想“大”:如果您确实有很多复杂的业务规则,那么构建“特定领域语言”是一种有时可能是降低复杂性的正确方法。只需通过谷歌搜索,您就会找到很多关于这个主题的材料。引用 David Wheeler 计算机科学中的所有问题都可以通过另一个间接层次来解决

【讨论】:

    【解决方案3】:

    原帖—— Martin Fowler 已经为您解决了这个问题……它被称为规范模式。
    http://en.wikipedia.org/wiki/Specification_pattern

    更新后的帖子——

    在以下情况下考虑使用复合规范模式:

    • 您需要根据某些条件选择对象子集,
    • 您需要检查是否只有合适的对象用于某个角色,或者
    • 您需要描述一个对象可能会做什么,而不需要解释对象如何做的细节

    模式的真正力量在于能够将不同的规范组合成具有 AND、OR 和 NOT 关系的组合。可以在设计时或运行时将不同的规范组合在一起。

    Eric Evan 关于领域驱动设计的书有一个很好的例子来说明这种模式(运输清单)

    这是维基链接:

    http://en.wikipedia.org/wiki/Specification_pattern

    wiki 链接的底部是这个 PDF 链接:

    http://martinfowler.com/apsupp/spec.pdf

    【讨论】:

    • 这种模式将一系列业务规则的编码/更改从开发人员转移到用户,从而为此类系统提供了非常高的灵活性。这可能会降低代码的复杂性,但另一方面,最终可能会以与原始代码之前的复杂程度相同的复合对象链结束。所以我怀疑这是否是问题的真正解决方案,它只是改变了维护业务规则的责任。
    • Doc,如果他们选择参数化规范,我同意你的看法。但是,与任何设计模式一样,它的实现方式可能会有所不同。规范有3种主要风格:1.硬编码规范(AKA GOF策略模式)2.参数化规范和3.复合规范(我会向用户推荐这条路线)这个链接支持我的陈述:martinfowler.com/apsupp/spec.pdf跨度>
    • 您提供的第二个链接以与您在回答中提供的维基百科链接不同、更通用的方式使用术语“规范”(那个链接只是关于“复合规范”)。如果您以这种更通用的方式表示“规范”,则应该更改答案中的链接。尽管如此,恕我直言,使用“复合规范”将问题从编码器转移到了用户(所以也许它为编码器解决了这个问题:-))。让 OP 决定这是否是他认为有用的。
    【解决方案4】:

    我认为该代码块本质上试图完成的是将食谱链接到该食谱中的成分。一种方法是在配方类本身中包含一个成分列表,然后将其与传入的成分列表进行比较,如下所示:

    public interface IRecipe {
       IEnumerable<Ingredient> Ingredients { get; }
    }
    
    public class Omlette : IRecipe {
       public IEnumerable<Ingredient> Ingredients { 
          get {
             return new Ingredient[]{new Ingredient("Salt"), new Ingredient("Egg")};
          }
       }
    }
    
    // etc. for your other classes.
    
    IRecipie FindRecipiesYouCanMake(IEnumerable<Ingredientes> stuff, Cook cook)
    {
        var query = Recipes.Where(r => !r.Ingredients.Except(stuff).Any());
        return query.First();
    }
    

    这假设您在某个地方收集了所有食谱。但是,设置这些列表的静态列表或从数据库中提取应该足够简单。

    有问题的 Linq 查询会查找所有传入 stuff 的成分都存在于成分列表中的任何食谱(或者,如前所述,成分中没有不在 stuff 中的成分)。这也可能会减少对 Recipes 的子类的需求,这似乎有点奇怪(尽管据我所知,您需要它还有其他原因)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-12
      • 2012-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-15
      • 1970-01-01
      相关资源
      最近更新 更多