“轻松加愉快”地实现并使用IComparer接口

发表于:2012-2-20 10:31

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:cyoooo7(cnblogs)    来源:51Testing软件测试网采编

  SortedList<T>,SortedSet<T>与SortedDictionary<T>都是我们常用的泛型类型。当T是我们自定义的类型时,往往该类型的默认比较行为不是我们所期望的。

  例如,我们有如下很老土的Employee类:

class Employee
   {
      public int Id { get; set; }
      public String Name { get; set; }
      public int Age { get; set; }
   }

  如果我们想让Employee类型按照Name字段升序排序,一般有两种做法。一种是让Employee实现IComparable或者IComparable<Employee>接口。不过此时如果我们又希望Employee按照别的方式排序的话,那只能另想办法了,因为IComparable或IComparable<Employee>接口所要求的Compare方法在Employee中只能有一个实现。另一种做法是构造一个比较器来指定Employee的比较规则。其中,比较器需要实现IComparer或IComparer<Employee>接口。

  在这个例子中,实现的方式可以是这样的:

//以Employee的Name字段为比较规则的比较器
   class EmployeeNameComparer:IComparer<Employee>
   {
      public int Compare(Employee x, Employee y)
      {
         return StringComparer.CurrentCulture.Compare(x.Name, y.Name);
      }
   }

  我们构造了一个EmployeeNameComparer类型,当我们这样使用SortedList<Employee>的时候:

//记录Employee电话号码的数据结构(虽然这么做有点傻,不过为了举例子还是将就一下吧)

  这个比较器类型可以指示SortedList,我们希望它以何种方式比较两个Employee对象。

  然而,当我们想让Employee按照Id或Age排序的时候,类似地,我们又会需要一个EmployeeAgeComparer和一个EmployeeAgeComparer:

//以Employee的Age字段为比较规则的比较器
   class EmployeeAgeComparer:IComparer<Employee>
   {
      public int Compare(Employee x, Employee y)
      {
          return x.Age - y.Age;
      }
   }

  EmployeeAgeComparer与EmployeeIdComparer的代码几乎一样。作为DRY(Don‘t Repeart Yourself)原则的忠实维护者,我们是不希望这样几乎一模一样的两份代码出现在我们的项目中的。因此是时候想办法重构了。

  上面所实现的比较器都在编译的时候就已经确定了它们本身的行为,也就是说它比较两个Employee时所使用的算法在编译时就已经定死了。然而我们真正想要的是一种通用的,能够在运行时指定其行为的比较器。

  为了能让同一个比较器能够拥有多种比较的方式,我们需要在运行时将比较的算法传递给它。在比较器的构造函数中向其传递算法,似乎很靠谱。而且在执行构造函数的过程中我们还能够锁定这个算法,以防止在比较器实例化之后,其比较行为还有机会被我们有意或无意地改变。

  接下来的问题是如何传递一个表示算法的参数?我们不仅要考虑该参数的有效性,还要考虑它的易用性(我们没有理由为了使用代表某种算法的参数,还需要为它特意构造一种类型吧?那也太麻烦了吧。)。基于这种情况,方法委托自然成了当仁不让的选择!

  好了,设计完毕。请看下面的比较器泛型:

public class DelegatedComparer<T> : IComparer<T>
   {
      //在构造函数结束后,_compare就没法改变了。因此DelegatedComparer<T>实例的行为也就可以被锁定了。
      private readonly Func<T, T, int> _compare;
     
      //传入一个委托,表示比较算法。
      public DelegatedComparer(Func<T, T, int> func)
      {
         _compare = func;
      }

      public int Compare(T x, T y)
      {
         //直接调用委托。
         return _compare(x, y);
      }
   }

  这个DelegatedComparer<T>泛型类型实现了IComparer<T>,其构造函数接受一个Func<T, T, int> func类型的委托为参数。这个委托可以用来指定比较器的比较算法。一切就是这么简单!

  我们可以这样使用它:

//记录Employee电话号码的数据结构(虽然这么做有点傻,不过为了举例子还是将就一下吧)
         var empTelTable = new SortedDictionary<Employee, String>(
            new DelegatedComparer<Employee>(
               (x, y) => x.Id - y.Id)
            );

  使用起来也非常简单!打字都少了很多吧? :-)

  您可能已经看出来了,这不就是Strategy模式吗?您说对了,我们就是利用了Strategy模式可以在运行时改变对象行为的能力,来完成动态指定比较算法的工作的。

  有了DelegatedComparer<T>,我们就可以轻松完成Employee对象之间的比较工作了。由于DelegatedComparer<T>是个泛型,所以我们甚至不需要把目光局限在(老土的)Employee身上,我们还可以对Customer类型,Student类型(都同样的老土)使用我们的DelegatedComparer<T>比较器。从此我们再也不需要为了某一种比较算法而专门实现一个类型了!

《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号