切尔斯基

http://liguanglei.name
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

On Extension Method: 扩展方法该如何使用

Posted on 2012-01-30 14:45  chelsea  阅读(1479)  评论(0编辑  收藏  举报

Herb Sutter 曾经有一个观点, 就是一个组件的接口, 不只包括这个组件本身定义的方法, 还包括使用这个组件的客户代码, 比如以这个组件为参数的那些方法. 扩展方法是对Herb Sutter这个观点所做的语法上的支持: 把以这个组件为参数的那些客户代码转变成扩展方法后, 调用时从语法上看起来跟调用这个组件本身定义的方法一模一样了!

---------------------现实的分割线------------------------

上面只是一厢情愿的历史溯源. 真实的情况是扩展方法是为了 LINQ 而加进来的, 就为了是表达式看起来干净漂亮些: videoList.Where(video => video.Rate > 18).Select(video => video.Title);

然而这种语法上的便利和优雅, 鼓励了一种原本在面向对象设计中就争论颇多的设计元素的更加随意的使用: 静态方法. 扩展方法只不过是抹了糖的静态方法.

我们需要整理一下扩展方法的应用场景.

 

考察一下LINQ. LINQ 为 IEnumerable 添加的扩展方法有几个特点:

  • 是业务无关的通用算法. 无论你是开发桌面应用还是web app, 无论是金融电信等行业软件, 还是博客聊天等个人工具, 都可以从这些通用的扩展方法中受益.

  • 是全局的, public的. 这应该是上一个特点带来的不得不这样的一种选择.

  • 目的是表达式的可读性

LINQ是对扩展方法的一种成功应用, 也是我所知的唯一一种大范围使用的应用. 鉴于扩展方法也原本是为LINQ而引入的, 所以你可以认为LINQ所代表的应用场景就是扩展方法应该的应用场景. 项目中较为成功的扩展方法应用是为 object 扩展了 ShouldBe 方法用于测试中, 比如:

public static void ShouldBe(this object actual, object expected)

{

    Assert.Equals(actual, expected);

}

用起来是 actual.ShouldBe(expected); 完全满足上面的三个特性.

 

再考察一下工作中曾经遇到的有争论的扩展方法的应用场景

最争议的是业务相关的逻辑应该不应该用扩展方法来表达, 业务模型应该不应该用扩展方法来扩展? General的回答是不应该. 毕竟是静态方法, 意味着某种业务概念的缺失, 应该有更好的封装方法. 但不妨以扩展方法开始: 曾经发生过的例子是我们最开始为HttpRequest创建了一个我们的业务相关的扩展方法, 而在接下来的几周内我们不断的为HttpRequest添加业务相关的扩展方法, 最终我们发现这几个方法合在一起其实是我们的一个领域概念.

在从扩展方法过渡到领域模型的过程中, 有一些跟上面几条不一样的规则:

  • 扩展方法应该是internal的, 因为是业务相关的, 必然只在当前Context下有效, 我们不希望在其它Context下它们也被IDE的智能提示带出来

  • 扩展方法所在的类不应该以所扩展的类或接口来命名, 比如不应该叫 HttpRequestExtensions, StringExtensions, 而应该以业务概念来命名. 这样更meaningful, 以后的过渡也会比较自然

  • 扩展方法应该放在靠近使用它的地方, 而不是放在它所扩展的类或接口旁边. 这是第一条规则的推论, 也是Herb Sutter理论的推论, 因为这种场景下的扩展方法的实现代码其实原本是客户代码, 本来就应该放在使用它的地方.

从这个角度来说, 扩展方法的出现是一个信号, 意味着所扩展对象的一个新的Context出现了. 因此扩展方法和 DCI 可以看作是对同一个问题的两种不同解答, 两者某种程度上都是为了解决可读性.

最后说个题外话, 扩展方法比一般实例方法有一个好处, 就是可以对象实例为null时可以不必抛出NullReferenceException, 因为此时对象实例是作为参数传进来, 可以做判断给出更有意义的异常.