有关virtual,override与扩展点的讨论

Virtual即虚拟。在C#中,Virtual指的是虚方法或虚函数。有关这个Virtual有一个经常被讨论的问题:所有的成员都应该是virtual的吗?

为保靖等地区用户提供了全套网页设计制作服务,及保靖网站建设行业解决方案。主营业务为做网站、网站制作、保靖网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!

这是一个由来已久的讨论,由于Java默认所有的方法都是可以被override的(除非手动写成final),因此从C#语言设计起初就有此番争论,甚至让Anders都出来解释了一下。最近又有人在讨论这方面话题了,虽然我的看法并没有超出这些人所涉及的范畴,但是我还是打算谈一下我的理解。退几步说,就当补充一些“实例”吧。

Virtual,Override与扩展点的关系简述

此次的话题是由Ward Bell引起的,他在review了Roy Osherove的新书《The Art of Unit Testing》之后认为,他不同意Roy给出的建议“将所有的成员默认为virtual”,为此他还独立开篇解释了他的观点。这篇文章引起的讨论较为热烈,我也打算在详细总结一番。与Ward观点对应的是,著名的Jeremy Miller希望.NET中所有的成员默认就是virtual的,而“月写博客80篇”的Oren Eini甚至认为所有的成员都应该标为virtual。

继承一个类并override掉其中的成员,是面向对象编程中最常用的方式之一。这是一种扩展方式,而能够被override的方法便是“扩展点”。所以我认为,是否把成员标记为virtual,其实涉及到的概念便是“是否把它开放为一个扩展点”。Oren认为“所有成员都应该virtual”则意味着“任何成员都是可扩展点”,而对于“默认为virtual”的观点来说,则意味着“倾向于打开更多的扩展点”——其实除了Oren有些极端外,“倾向性”代表的更多是一种“口味”,因为无论是Java还是.NET,都可以标记一个成员能否被override。

从Virtual,Override与扩展点延伸谈去

我不想讨论“口味”问题,不过我的观点与Ward类似,即使在C#出现之前,我也一直不太喜欢Java的这个特性(不过当时相关体会比较少,所以感觉并不强烈)。Oren认为打开更多扩展点,有助于从各方面进行扩展,他说他的这个做法也过于也得到了较多的“实惠”。不过我认为,这是由于Oren的能力过于厉害,并且知道该做什么不该做什么,并且可以对自己作的事情所负责决定的。

对于一个可“全面扩展”的类型来说,意味着开发人员有更多的自由,进而意味着选择(即使是做同一件事情)。但是选择多,则同样意味着我们需要了解的多,一个不慎可能就会发现没有得到预期的效果。例如,在继承了ASP.NET的Control类之后,您要改变它输出的内容,您会选择覆盖哪一个方法?

 
 
 
  1. protected internal virtual void Render(HtmlTextWriter writer)  
  2. {  
  3.     this.RenderChildren(writer);  
  4. }  
  5.  
  6. protected internal virtual void RenderChildren(HtmlTextWriter writer)  
  7. {  
  8.     ...  
  9. }  

您可能会说,覆盖哪个都可以。但是,它们其实都有不同的语义,只是因为在Control基类中Render自身就是Render所有的子控件。但是到了子类中,Render自身可能就会涉及到边框等其他内容了。如果您随便选一个,您的类型看上去没有问题,但是如果别人希望继承你写的类,补充一些实现,那么你的“选择”就会影响到他的结果了。当然我并不是说Control类设计的不对,它的设计我觉得是正确的,我只是想说明,如果每个成员都可扩展,那么用户在真正需要扩展的时候就会比较麻烦了。

架构是一种扩展,也是一种约束,限制了别人可以怎么做,也必须怎么做。我们虽然无法避免别人的恶意行为,但是良好的扩展点也可以给别人更好的指导。

再举一个例子。在.NET中,最容易扩展的抽象元素是什么呢?应该是“接口”。接口中的所有成员都是由实现方提供的,除了成员的签名之外,接口并没有作任何限制。正如我在之前写过的一篇文章里提到,别人完全可以实现出外强中干的对象:

 
 
 
  1. public interface IList  
  2. {  
  3.     void Add(T item);  
  4.     int Count { get; }  
  5.    
  6.     ...  

根据接口中隐含的协议,Add方法调用之后,Count必须加一。但是这个协议并无法加诸于实现之上。如果要提供这方面的约束,我们只能公开一部分的扩展点,而不是把所有的职责交给实现方:

 
 
 
  1. public abstract class ListBase  
  2. {   
  3.     public int Count { getprivate set; }  
  4.    
  5.     public void Add(T item)  
  6.     {  
  7.         this.Count++;  
  8.         this.AddCore(item);  
  9.     }  
  10.    
  11.     protected abstract void AddCore(T item);  
  12.    
  13.     ...  

Ward也举了一个“电梯”的例子。电梯有一个Up方法,调用它则意味着电梯上升。但是如果把Up这个关键行为扩展出去,那么别人在“修改电路”(即override这个Up方法)的时候,可能就会把电梯搞乱套了,例如原本应该先关门再启动,现在可能先启动再关门,甚至一旦Up电梯就下降了。Oren认为扩展方应该为自己的扩展负责,但是我还是认为,扩展点应该和成员访问级别等东西一样,只给出必要的,控制住关键的。

最后的例子也是常见的:

 
 
 
  1. public class SomeClass  
  2. {  
  3.     public void SomeMethed()  
  4.     {  
  5.         this.SomeMethed(String.Empty);  
  6.     }  
  7.  
  8.     public void SomeMethed(string s)  
  9.     {  
  10.         this.SomeMethod(s, 0);  
  11.     }  
  12.  
  13.     public virtual void SomeMethod(string s, int i)  
  14.     {  
  15.         // ...  
  16.     }  
  17. }  

为了方便起见,我们常常会对类型中的方法给出重载,其中大部分的重载最终都委托给一个唯一的核心方法。当用户继承SomeClass类之后,他便拥有了一个唯一的扩展点,这样便可以确保这个类的行为按照一定的“准则”在正常开展。否则的话,用户就需要在三个方法中进行选择性的override,并且要平衡三者的行为。因为在扩展SomeClass的时候,并不知道SomeClass的使用者会调用SomeMethod的哪个重载。

这对于单元测试一样。如果三个方法都可以Mock,那么在测试时我们可能就会去“猜测”用户究竟调用了哪个SomeMethod重载,而这是不确定的,也是容易变化的。如果我们只有一个重载可以Mock,那么则意味着“别挑了,就是这个”。所以,我有时候也不太喜欢Type Mock如此强大有力的Mock框架,因为它可能会破坏了被测试方的设计,把一切都变成了扩展点——虽然这对于测试来说的确很方便,几乎不输给动态语言了。

“可测试性”也是设计出来,不是语言或平台自动赋予的。这就是“design for testability”的体现之一吧。

以上就是有关virtual,override与扩展点的一些讨论。本文来自老赵点滴:《所有的成员都应该是virtual的吗?》

当前名称:有关virtual,override与扩展点的讨论
文章链接:http://www.hantingmc.com/qtweb/news23/212023.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联