Hello World
Spiga

趣味编程:C#中Specification模式的实现

2009-09-15 20:15 by 老赵, 14496 visits

今天有朋友在问了我这么一个问题:怎么实现OrWhere的功能?我猜测,他的意思是要实现这样的功能:

static IEnumerable<int> MorePredicate(IEnumerable<int> source)
{
    return source.OrWhere(i => i > 0); // 或所有的正数
}

static void Main(string[] args)
{
    var array = Enumerable.Range(-5, 10).ToArray();
    var odd = array.Where(i => i % 2 != 0);  // 排除所有偶数
    var oddOrPositive = MorePredicate(odd);

    foreach (var i in oddOrPositive)
    {
        Console.WriteLine(i);
    }
}

以上代码应该输出-5,-3,-1,1,2,3,4。但显然,这段代码是无法编译通过,无法通过补充类库来实现的。因为在Main方法中的Where过后,已经排除了所有的偶数,于是在接下来的代码中是无法从新的序列中再次恢复元素的。

不过这个要求让我想起了Specification模式。Specification模式的作用是构建可以自由组装的业务逻辑元素。Specification类有一个IsSatisifiedBy函数,用于校验某个对象是否满足该Specification所表示的条件。多个Specification对象可以组装起来,并生成新Specification对象,这便可以形成高度可定制的业务逻辑。例如,我们可以使用依赖注入(控制反转)的方式来配置这个业务逻辑,以此保证系统的灵活性。

如果您点开上面那个Wikipedia的链接,就会发现这段描述大约是一个英译中的练习。抄完了“定义”,再来看看同样引自Wikipedia的UML图:

specification-pattern-uml

一般来说,Specification模式则意味着实现这样一个接口:

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T candidate);

    ISpecification<T> And(ISpecification<T> other);

    ISpecification<T> Or(ISpecification<T> other);

    ISpecification<T> Not();
}

实现了ISpecification的对象则意味着是一个Specification,它可以通过与其他Specification对象的And,Or或对自身的取反来生成新的逻辑。为了方便这些“组合逻辑”的开发,我们还会准备一个抽象的CompositeSpecification类:

public abstract class CompositeSpecification<T> : ISpecification<T>
{
    public abstract bool IsSatisfiedBy(T candidate);

    public ISpecification<T> And(ISpecification<T> other)
    {
        return new AndSpecification<T>(this, other);
    }

    public ISpecification<T> Or(ISpecification<T> other)
    {
        return new OrSpecification<T>(this, other);
    }

    public ISpecification<T> Not()
    {
        return new NotSpecification<T>(this);
    }
}

CompositeSpecification提供了构建复合Specification的基础逻辑,它提供了And、Or和Not方法的实现,让其他Specification类只需要专注于IsSatisfiedBy方法的实现即可。例如,以下便是三种逻辑组合的具体实现:

public class AndSpecification<T> : CompositeSpecification<T>
{
    private ISpecification<T> m_one;
    private ISpecification<T> m_other;

    public AndSpecification(ISpecification<T> x, ISpecification<T> y)
    {
        m_one = x;
        m_other = y;
    }

    public override bool IsSatisfiedBy(T candidate)
    {
        return m_one.IsSatisfiedBy(candidate) && m_other.IsSatisfiedBy(candidate);
    }
}

public class OrSpecification<T> : CompositeSpecification<T>
{
    private ISpecification<T> m_one;
    private ISpecification<T> m_other;

    public OrSpecification(ISpecification<T> x, ISpecification<T> y)
    {
        m_one = x;
        m_other = y;
    }

    public override bool IsSatisfiedBy(T candidate)
    {
        return m_one.IsSatisfiedBy(candidate) || m_other.IsSatisfiedBy(candidate);
    }
}

public class NotSpecification<T> : CompositeSpecification<T>
{
    private ISpecification<T> m_wrapped;

    public NotSpecification(ISpecification<T> x)
    {
        m_wrapped = x;
    }

    public override bool IsSatisfiedBy(T candidate)
    {
        return !m_wrapped.IsSatisfiedBy(candidate);
    }
}

于是,我们便可以使用Specification模式来处理刚才那位朋友的问题。例如,首先他需要排除所有的偶数,那么我们不妨实现一个OddSpecification:

public class OddSpecification : CompositeSpecification<int>
{
    public override bool IsSatisfiedBy(int candidate)
    {
        return candidate % 2 != 0;
    }
}

自然,还有用于获得所有正数的Specification类:

public class PositiveSpecification : CompositeSpecification<int>
{
    public override bool IsSatisfiedBy(int candidate)
    {
        return candidate > 0;
    }
}

于是在使用时,我们会将其通过Or方法组合起来:

static ISpecification<int> MorePredicate(ISpecification<int> original)
{
    return original.Or(new PositiveSpecification());
}

static void Main(string[] args)
{
    var array = Enumerable.Range(-5, 10).ToArray();
    var oddSpec = new OddSpecification();
    var oddAndPositiveSpec = MorePredicate(oddSpec);

    foreach (var item in array.Where(i => oddAndPositiveSpec.IsSatisfiedBy(i)))
    {
        Console.WriteLine(item);
    }
}

但是,您觉得这个做法怎么样?我觉得过于杀鸡用牛刀,高射炮打蚊子了。Specification模式虽然常用,但是用在这里太重量级了。如果我们为每一个函数都补充一个Specification类,至少会让我感到厌倦。

以上的代码其实转载自Wikipedia词条,不过我修改了一些命名,以及改写成泛型版本。我们有理由推测,Wikipedia上是非常旧的内容,很可能是在C#只是1.0版本的时候编写的代码(或者说它为了“兼容”Java那种语言的实现方式)。那么在实际开发过程中,我们又该如何利用C#如今的强大特性来实现出更容易使用,甚至是更为“轻量级”的Specification模式呢?

此外,刚才又收到那位朋友的消息,他其实是想在使用LINQ to SQL时实现“可扩展的逻辑”。那么,对于他说的情况,我们又该如何应对呢?

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

57 条回复

  1. 老赵
    admin
    链接

    老赵 2009-09-15 20:48:00

    写完,回家!

  2. 非空
    *.*.*.*
    链接

    非空 2009-09-15 21:00:00

    看完 闪人

  3. kimi.hua
    *.*.*.*
    链接

    kimi.hua 2009-09-15 21:12:00

    支持一下,消化ing

  4. Zhenway
    *.*.*.*
    链接

    Zhenway 2009-09-15 21:15:00

    orwhere+linq to sql似乎只能用拼Expression Tree的方式了吧

  5. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-15 21:28:00

    我宁可用临时存储过程 哈哈

  6. ephon[未注册用户]
    *.*.*.*
    链接

    ephon[未注册用户] 2009-09-15 21:36:00

    如果表达式是根据用户的选择形成的呢:
    比如
    用户A: (>100 and %3=0) or (<100 and %2=0)
    用户B: ((>110 and %3=0) or (<110 and %2=0) ) && ( 其他的表达式略去...)

    这些表达式都是用户决定的:
    那么,程序这边怎么生成:想要的spe呢?

    :) 不知道我说的明白不?

  7. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-15 21:42:00

    左右我看Specification 眼熟
    似乎是Linq 应该有所实现的东西

  8. 老赵
    admin
    链接

    老赵 2009-09-15 22:15:00

    @ephon
    你的意思是,把字符串解析为Spec对象?

  9. 老赵
    admin
    链接

    老赵 2009-09-15 22:16:00

    @韦恩卑鄙
    写一个通用的存储过程,和一个通用的LINQ Provider相比难度可能差不多,嘿嘿。
    不过一般项目里不用太通用的。

  10. iceboundrock
    *.*.*.*
    链接

    iceboundrock 2009-09-15 22:19:00

    也许用Dynamic Linq是比较简单的解决方式。
    参考这里

  11. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-15 22:32:00

    @iceboundrock
    我本也想建议 但是忘记Dynamic怎么拼了

    不过这个库反射成魔 效率貌似不如表达式树

  12. 老赵
    admin
    链接

    老赵 2009-09-15 22:36:00

    @韦恩卑鄙
    不是反射成魔,是在构造表达式树时必要的过程。
    其实它也只是构造一个表达式树的数据结构。
    效率肯定不如编译期就确定的数据结构,毕竟是在解析字符串。

  13. 老赵
    admin
    链接

    老赵 2009-09-15 22:39:00

    @非空
    没有做一下的想法吗?

  14. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-15 22:49:00

    Jeffrey Zhao:
    @韦恩卑鄙
    不是反射成魔,是在构造表达式树时必要的过程。
    其实它也只是构造一个表达式树的数据结构。
    效率肯定不如编译期就确定的数据结构,毕竟是在解析字符串。


    我的意思是 Dynanmic Linq 那个库 把解析字符串的结果变成expression tree的时候 用了不少反射 我第一次看那个代码的时候 很费力气5555

    如果是从UI接收的就是字符串还好 如果本来就要建立Expression Tree 那部分反射其实是不需要的,感觉似乎直接着手就好

  15. iceboundrock
    *.*.*.*
    链接

    iceboundrock 2009-09-15 22:53:00

    @韦恩卑鄙
    哦,我倒是没看代码。不过方法调用的效率应该不太容易成为瓶颈吧,如果确实发现这里有问题,也许引入老赵的FastReflectionLib能改善一下。

  16. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-15 22:56:00

    >_< 广告无处不在

  17. 老赵
    admin
    链接

    老赵 2009-09-15 23:02:00

    @iceboundrock
    dynamic linq主要工作是解析字符串,如果说用了反射,那都是那些GetMethod,GetProperty之类的调用,其实并不是FastReflectionLib的适用场景。
    FastReflectionLib优化的是Invoke相关操作,呵呵。

  18. jacky.w[未注册用户]
    *.*.*.*
    链接

    jacky.w[未注册用户] 2009-09-15 23:02:00

    我以为和DDD相关:)

  19. 老赵
    admin
    链接

    老赵 2009-09-15 23:06:00

    @Zhenway
    是啊是啊,这块也是练习表达式树的操作。

  20. 老赵
    admin
    链接

    老赵 2009-09-15 23:08:00

    @jacky.w
    趣味编程的主要目的只是用来练习编程的,呵呵。

  21. .....写完了[未注册用户]
    *.*.*.*
    链接

    .....写完了[未注册用户] 2009-09-15 23:12:00

    都写完了.还不回家吃饭
    小心妈妈叫你回家吃饭

    都看完了.还不闪回家家吃饭
    小心妈妈叫你回家吃饭

  22. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-09-15 23:16:00

    老赵的文章让我越来越需要时间去消化了

  23. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-09-15 23:32:00

    @Jeffrey Zhao
    看到第一句就想起了以前写的批量重命名文件的程序
    本来是用来练WPF
    其中一个文件,当时还写了些有趣的注释在代码里:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;
    using FxUtil;
    
    namespace BatchRenameUtility {
      public static class RenameHelper {
        public static void Rename(string srcFileName, string destFileName) {
          if (srcFileName == destFileName) return;
          //File.Move(srcFileName, destFileName);
          // TODO! testing funtionality
          Console.WriteLine("Src={0}, Dest={1}", srcFileName, destFileName);
        }
    
        public static void BatchRename(IEnumerable<FileInfo> files, IEnumerable<IFileNameGenerator> generators, IEnumerable<IFilterProvider> filters, bool isDefaultInclude, IEnumerable<IOrderingProvider> orderings) {
          // guards
          // skip the rest of the method if there's nothing to rename,
          // or if there's no available way to generate a new file name
          if (null == files || 0 == files.Count() || null == generators || 0 == generators.Count()) return;
    
          var renameList = files.AsQueryable<FileInfo>();
    
          // build the filtering query clauses
          // skip predicate construction if there's no filters
          if (null != filters && 0 != filters.Count()) {
            var predicate = BuildPredicate(filters, isDefaultInclude);
            renameList = renameList.Where(predicate);
          }
    
          // build the ordering query clauses
          // skip sorting if there's no order key specified
          if (null != orderings && 0 != orderings.Count()) {
            // the following code resembles the DynamicQuery sample
            var queryExpr = renameList.Expression;
            var parameters = new ParameterExpression[ ] {
                Expression.Parameter(renameList.ElementType, string.Empty) };
            var methodAsc = "OrderBy";
            var methodDesc = "OrderByDescending";
    
            // Thank God! I had the hell of a time getting this part of code right
            // Thanks to the documentation at MSDN:
            // http://msdn.microsoft.com/en-us/library/bb882637.aspx
            // I wasn't clear what the Type array was for; I thought it's supposed
            // to correspond to the arg array...but it wasn't. It corresponds to
            // the generic params in Func<...>.
            // The Expression.Call() + queryExpr.Provider.CreateQuery() is
            // exactly what happens in Queryable.OrderBy<TSource,TKey>,
            // we're just replicating it by hand here.
            foreach (var o in orderings) {
              queryExpr = Expression.Call(
                typeof(Queryable),
                o.IsAscending ? methodAsc : methodDesc,
                new Type[ ] { renameList.ElementType, o.KeyType },
                queryExpr, Expression.Quote(o.KeySelector));
              methodAsc = "ThenBy";
              methodDesc = "ThenByDescending";
            }
            renameList = renameList.Provider.CreateQuery<FileInfo>(queryExpr);
          }
    
          // do the renaming
          // (skip the file if no compatible file name generator is available)
          foreach (var file in renameList) {
            var gen = generators.FindCompatibleGenerator(file);
            if (null != gen) Rename(file.Name, gen.Generate(file));
          }
        }
    
        private static Expression<Func<FileInfo, bool>> BuildPredicate(IEnumerable<IFilterProvider> filters, bool isDefaultInclude) {
          var seed = isDefaultInclude ? PredicateBuilder.True<FileInfo>() : PredicateBuilder.False<FileInfo>();
          return filters.Aggregate(seed, (pred, filter) =>
                        FilterCombineOperation.And == filter.Combine ?
                        pred.And(filter.Predicate) :
                        FilterCombineOperation.Or == filter.Combine ?
                        pred.Or(filter.Predicate) :
                        pred);
        }
    
        private static IFileNameGenerator FindCompatibleGenerator(this IEnumerable<IFileNameGenerator> generators, FileInfo info) {
          return generators.FirstOrDefault(gen => gen.IsCompatible(info));
        }
      }
    }
    

  24. 老赵
    admin
    链接

    老赵 2009-09-16 00:40:00

    @RednaxelaFX
    我现在只想说,博客园允许在评论中的插入代码实在是太合适了。

  25. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-09-16 01:04:00

    @Jeffrey Zhao
    但是字数限制使贴代码变得很麻烦咯。为了降低字数,我代码原本的换行和空格之类的格式都给剪了大半,连缩进都缩成俩空格了 T T

    前面不够字数说明,这里补充点。我最初写那个批量重命名文件程序是想提供一些简单的功能,楼上的邻居叫我写一个能按时间自动把某目录下的文件改为001、002之类的序号,我就做了个。然后添加了根据各种文件属性来排序,以及根据正则表达式的组合来匹配并重命名文件的功能。然后觉得这样太土了,干脆做个插件架构,让第三方能够提供插件来增添提供文件属性顺序、文件名匹配和文件名生成等功能,然后我在程序里获取到所有已发现的插件后显示在界面上让用户勾选、决定先后顺序、决定And/Or/Not的组合方式,将各插件的功能组合起来应用在一次批量重命名中。再后来干脆整合了IronPython让高级用户直接写些脚本来指定“插件”……

    上面的代码就是那程序某个中期版本的测试用代码。FilterCombineOperation是个enum,就是指定And/Or/Not的,主要为了数据绑定方便。我现在才知道有个specification模式,土包了啊 T T

  26. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-16 07:40:00

    自己总结出来的模式绝对不是土包
    是great minds think alike

  27. mint2003[未注册用户]
    *.*.*.*
    链接

    mint2003[未注册用户] 2009-09-16 08:08:00

    强烈抗议小赵岐视IE 6 用户!!!!!!

  28. 国士无双
    *.*.*.*
    链接

    国士无双 2009-09-16 09:22:00

    array.Where(i => i % 2 != 0 || i > 0)不行么?

  29. 老赵
    admin
    链接

    老赵 2009-09-16 09:47:00

    @国士无双
    OrWhere的逻辑需要在外部指定,它可能是OrWhere,也可能是其他附加的修饰。

  30. B.T.Q
    *.*.*.*
    链接

    B.T.Q 2009-09-16 10:11:00

    http://en.wikipedia.org/wiki/File:Specification_UML.png
    这张图右下角的子类,是不是名字打错了?两个And子类?
    应该是NotSpecification吧

  31. 老赵
    admin
    链接

    老赵 2009-09-16 10:13:00

    @B.T.Q
    是的,图上画错了。

  32. 小城故事
    *.*.*.*
    链接

    小城故事 2009-09-16 10:19:00

    相当于说某个东西我不要,去掉,之后又后悔了说其实去掉的要是凑合的话还应该留着,只能在哪里丢掉哪里找。

                var array = Enumerable.Range(-5, 10).ToArray();
                var odd = array.Where(i => i % 2 != 0);  // 排除所有偶数
                array
                    .Where(c => !odd.Contains(c) && c > 0)
                    .Union(odd)
                    .OrderBy(c => c)
                    .ToList()
                    .ForEach(n=>Console.WriteLine(n));
    
                Console.Read();
    

    只要odd的源array还没被回收。

  33. 老赵
    admin
    链接

    老赵 2009-09-16 10:22:00

    @小城故事
    其实不应该是GroupBy,Distinct()就可以了。
    不过这篇文章是想搞Specification模式,解决那位朋友最好的办法也是(Lightweight)Specification模式。

  34. 小城故事
    *.*.*.*
    链接

    小城故事 2009-09-16 10:28:00

    不好意思写错了,是OrderBy不是GroupBy。
    Specification模式,第一次听说,解决了我以前一个大疑惑。

  35. 植辉
    *.*.*.*
    链接

    植辉 2009-09-16 10:32:00

    之前没听说过specification模式,看了老赵代码后怎么感觉有点像Decorator模式和Visitor模式的结合。

  36. 老赵
    admin
    链接

    老赵 2009-09-16 10:35:00

    @植辉
    天下模式一大抄,你方抄罢我来抄。
    其实技术都是这样,都是有关系的。

  37. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-09-16 11:11:00

    LINQ里面可以用Union。

  38. 老赵
    admin
    链接

    老赵 2009-09-16 11:15:00

    @Ivony...
    可能你没有理解文章的意思……

  39. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-09-16 11:32:00

    @Jeffrey Zhao

    呵呵,我是冲着问题去的。


    关于Specification模式我倒是真的有话要说。

    首先个人对Pattern翻译成模式就相当的有话要说。pattern这个词的本意应该是是模范、范本、样板,翻译成模式应该说很不恰当。

    其次对这种发明各种名词来阐述这些设计样本(design-pattern)的风气也相当的不感冒。写程序的目的是解决问题,只要能解决问题,根本就不必关心这个解决问题的方法是叫做什么pattern。

    或许老赵在写这篇文章的时候,提到Specification模式只是想使得自己的表述更为专业(或许发明这个词的人本意也是如此)。但却不可避免的使得很多程序员努力的记住这个单词的拼写而忽略了这个样本的实现和设计思想及应用场景。


    最后,容许我发表一点个人观点,我对那个发明这这个破名字的老外相当不满意。。。。Over。

  40. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-09-16 11:42:00

    我觉得这个模式可以继续抽象成为,任何两个对象同类型的组合起来成为同类型的新对象,新对象包含原有两个对象的特性这样的手法。很明显,这样的话,MulticastDelegate也是这种思想的产物。当然也包括我的论坛的权限控制系统,话说我也有IsSatisified方法。

    AND、OR、NOT这三种方式过于僵化,应该将组合逻辑进行剥离,这是不是又变出新的模式了?

  41. 老赵
    admin
    链接

    老赵 2009-09-16 11:44:00

    @Ivony...
    And,Or,Not这种的确比较僵化,如果是复杂的话,和上面提到的Decorator就很像了。

  42. CoolCode
    *.*.*.*
    链接

    CoolCode 2009-09-16 12:51:00

    我在一个外国朋友的代码基础上,源代码:

    
            public static Expression<Func<T, bool>> True<T>() { return f => true; }
            public static Expression<Func<T, bool>> False<T>() { return f => false; }
    
            public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
                                                                Expression<Func<T, bool>> expr2) ...
    

    打造成下面的动态查询:
        /// <summary>
        /// 动态查询条件创建者
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public interface IQueryBuilder<T>
        {
            Expression<Func<T, bool>> Expression { get; set; }
        }
    ...
     /*建立查询条件*/
             var qb = QueryBuilder.Create<User>()
                    .Between(c => c.UserID, txtByrCdFr.Text, txtByrCdTo.Text)
                    .Like(c => c.UserName, txtByrNam.Text)
                    .Equals(c => c.UserStatus, ddlStatus.SelectedValue 
           ...
    
      // qb 是 IQueryBuilder<User> 类型
      //服务中使用
      如:table.Where(qb.Expression);
    

    有空再写个文章公开一下,已经使用到实际开发中。

  43. 老赵
    admin
    链接

    老赵 2009-09-16 13:07:00

    @CoolCode
    查不多就是类似的东西,不过大家最好自己写一下,当作练习,赫赫。

  44. 小城故事
    *.*.*.*
    链接

    小城故事 2009-09-16 13:51:00

    终于有点明白老赵意思了,于是这样实现,不知道对不对?
    先新建两个类

        //用委托初始化的查询条件
        public class FuncSpecification<T> : CompositeSpecification<T>
        {
            public Func<T, bool> Predicate { get; set; }
    
            public FuncSpecification(Func<T, bool> _predicate)
            {
                Predicate = _predicate;
            }
    
            public override bool IsSatisfiedBy(T candidate)
            {
                return Predicate(candidate);
            }
        }
        //保存查询条件和集合上下文的类
        public class SpecifyingEnumerable<T>
        {
            public SpecifyingEnumerable(IEnumerable<T> source, ISpecification<T> specification)
            {
                Source = source; Specification = specification;
            }
    
            public IEnumerable<T> Source { get; set; }
            public ISpecification<T> Specification { get; set; }
        }
    

    然后是支持添加查询的扩展方法:
        static class SpecifyExtension
        {
            internal static SpecifyingEnumerable<TSource> BeginWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
            {
                var specification = new FuncSpecification<TSource>(predicate);
                return new SpecifyingEnumerable<TSource>(source, specification);
            }
    
            internal static SpecifyingEnumerable<TSource> AndWhere<TSource>(this SpecifyingEnumerable<TSource> source, Func<TSource, bool> predicate)
            {
                var funcSpecification = new FuncSpecification<TSource>(predicate);
                source.Specification = new AndSpecification<TSource>(source.Specification, funcSpecification);
                return source;
            }
    
            internal static SpecifyingEnumerable<TSource> OrWhere<TSource>(this SpecifyingEnumerable<TSource> source, Func<TSource, bool> predicate)
            {
                var funcSpecification = new FuncSpecification<TSource>(predicate);
                source.Specification = new OrSpecification<TSource>(source.Specification, funcSpecification);
                return source;
            }
    
            internal static SpecifyingEnumerable<TSource> NotWhere<TSource>(this SpecifyingEnumerable<TSource> source)
            {
                source.Specification =  new NotSpecification<TSource>(source.Specification);
                return source;
            }
    
            internal static IEnumerable<TSource> EndWhere<TSource>(this SpecifyingEnumerable<TSource> source)
            {
                return source.Source.Where(c => source.Specification.IsSatisfiedBy(c));
            }
        }
    

    然后那个例子就可以这样实现:
                var array = Enumerable.Range(-5, 10);
                array
                    .BeginWhere(i => i % 2 != 0)
                    .OrWhere(c => c > 0)
                    .EndWhere()
                    .ToList()
                    .ForEach(n=>Console.WriteLine(n));
                Console.Read();
    

  45. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-09-16 15:22:00

    我觉得这样就可以了:

    var array = Enumerable.Range(-5,10).ToArray();
    var odd = new FuncSpecification<int>(i => i % 2 != 0);
    var positive = new FuncSpecification<int>(i => i > 0);
    var oddAndPositive = odd.Or(positive);
    foreach (var item in array.Where(i => oddAndPositive.IsSatisfiedBy(i)))
    {
          Console.WriteLine(item);
    }
    Console.ReadLine();
    

  46. 老赵
    admin
    链接

    老赵 2009-09-16 15:26:00

    @麒麟.NET
    不错不错,赫赫。

  47. 老赵
    admin
    链接

    老赵 2009-09-16 15:26:00

    @小城故事
    你这个不是Specification模式啊。

  48. 稻草人
    *.*.*.*
    链接

    稻草人 2009-09-16 15:41:00

    看出来了,老赵是写手.

  49. 小城故事
    *.*.*.*
    链接

    小城故事 2009-09-16 15:45:00

    @Jeffrey Zhao
    怎么不是Specification模式,只是把Specification封装了。
    反正这个模式是您提出来的,您说不是也罢,只要更容易使用就行。我想多数朋友还是倾向于我那种用法的。

  50. 老赵
    admin
    链接

    老赵 2009-09-16 15:49:00

    @小城故事
    不觉得,其实楼上麒麟.NET的已经OK了,你这里又把问题复杂化了。
    而且,你都绑定在IEnumerable<T>上,怎么会是Specification模式呢?

  51. 小城故事
    *.*.*.*
    链接

    小城故事 2009-09-16 15:55:00

    @Jeffrey Zhao
    如果就像麒麟兄那样,那老赵花那么多时间写文章,最后提出的问题也太简单了吧。

  52. 老赵
    admin
    链接

    老赵 2009-09-16 15:58:00

    @小城故事
    我最后答案有三部分,都很简单,麒麟兄给出的是一部分。
    而且,问题复杂,答案简单的情况也不少吧,我的“趣味编程”大部分都是这样的。

  53. 小城故事
    *.*.*.*
    链接

    小城故事 2009-09-16 17:39:00

    @Jeffrey Zhao
    可能是老赵把答案的框架都基本实现了,其实答案就算简单,也可以引出新问题的。大家都想通过一个问题的解决,得到最大的收获。

  54. 老赵
    admin
    链接

    老赵 2009-09-16 17:56:00

    @小城故事
    我觉得还是把答案本身搞透彻,再去延伸吧。
    其实我设想中的答案本身已经很灵活了,你的延伸并没有什么好处啊。
    可能说不清楚,要不还是等我的参考答案吧,呵呵。

  55. chinajuanbob
    *.*.*.*
    链接

    chinajuanbob 2009-09-17 10:15:00

    麒麟.NET:
    我觉得这样就可以了:


    土了,FuncSpecification<T>是个啥东东?

  56. Jeff Tian
    210.13.83.*
    链接

    Jeff Tian 2014-04-25 14:32:00

    如何在 Specification 模式里加入 OrderBy 的 Specification。即一般的 Specification 是给 Where 筛选用的,除了这个我还想传入一个 Specification 给 OrderBy 来排序用。大概想这样来用

    var findSpec = ...;
    var sortSpec = ...;
    schoolRepository.Find(findSpec.And(sortSpec));
    

    谢谢各位大侠支招解答!

  57. 链接

    satan.student 2014-08-19 19:21:00

    其实原需求可以像Linq to SQL那样,先保留Expression Tree,等到最后一步的时候再分析Expression Tree,进行代码上的变换,得到需要的业务组合。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我