Hello World
Spiga

所有的成员都应该是virtual的吗?

2009-08-28 10:56 by 老赵, 7551 visits

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

此次的话题是由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。

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

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

protected internal virtual void Render(HtmlTextWriter writer)
{
    this.RenderChildren(writer);
}

protected internal virtual void RenderChildren(HtmlTextWriter writer)
{
    ...
}

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

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

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

public interface IList<T>
{
    void Add(T item);
    int Count { get; }
 
    ...
}

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

public abstract class ListBase<T>
{ 
    public int Count { get; private set; }
 
    public void Add(T item)
    {
        this.Count++;
        this.AddCore(item);
    }
 
    protected abstract void AddCore(T item);
 
    ...
}

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

最后的例子也是常见的:

public class SomeClass
{
    public void SomeMethed()
    {
        this.SomeMethed(String.Empty);
    }

    public void SomeMethed(string s)
    {
        this.SomeMethod(s, 0);
    }

    public virtual void SomeMethod(string s, int i)
    {
        // ...
    }
}

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

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

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

您的观点呢?

Creative Commons License

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

Add your comment

70 条回复

  1. Ryan Gene
    *.*.*.*
    链接

    Ryan Gene 2009-08-28 11:00:00

    我希望都是virtual的,利于扩展

  2. 幽梦新影
    *.*.*.*
    链接

    幽梦新影 2009-08-28 11:05:00

    我对这东西也十分不理解,就好像NHibernate中的所有属性都是得是Virtual,干嘛要这样设计,应该是你喜欢Virtual就Virtual,不喜欢拉倒

  3. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-08-28 11:06:00

    我讨厌都是virtual的,多次就这个问题和同事讨论,JAVAER 有Java的观点。

    感觉现在C#的设计真的没啥不好的。

  4. 老赵
    admin
    链接

    老赵 2009-08-28 11:09:00

    @Ryan Gene
    所以JavaScript合你心意,想咋搞就咋搞,嘿嘿。

  5. 老赵
    admin
    链接

    老赵 2009-08-28 11:25:00

    @幽梦新影
    我觉得NH的要求很无厘头,居然强制virtual,即使是我们不需要映射的辅助属性也要virtual,真的很恶心。
    这也是我打算写的话题。

  6. 欧阳雨凡[未注册用户]
    *.*.*.*
    链接

    欧阳雨凡[未注册用户] 2009-08-28 11:25:00

    说白了,这是一种规则契约,既然是契约就是有限制的,没有任何限制,到最后反倒可能什么也做不了,我还是倾向于有限制的扩展.

    事实上语言给了用户这种自由,如果类,接口,抽象类的设计者有这样的需求,他也确信他的后续使用完全可以驾驭,他也完全可以把所有的成员显示设计成virtual.

  7. James.Ying
    *.*.*.*
    链接

    James.Ying 2009-08-28 11:38:00

    这问题还真难下结论,如果全virtual,我是不能接受的,但说到扩展,也确实有点道理,可能还是看具体需求来定吧。

  8. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-08-28 11:40:00

    你做什么的时候,必须清楚你在做什么。
    像C的指针,清楚到内存的每一个字节。
    大部分人就只follow就行了。

  9. billlo[未注册用户]
    *.*.*.*
    链接

    billlo[未注册用户] 2009-08-28 11:47:00

    老趙最近的文章都很實用,解決一些實際工作問題

  10. 寒星
    *.*.*.*
    链接

    寒星 2009-08-28 12:00:00

    个人不赞成提供缺省成员全virtual,应该是由设计人员决定哪些需要提供可扩展性。

  11. Jun1st
    *.*.*.*
    链接

    Jun1st 2009-08-28 12:00:00

    个人认为,是否全是virtual对于系统设计和应用来说,并不能有保证你得到什么好处或是坏处。
    关键是看使用者的个人能力和对virtual的理解

  12. 老赵
    admin
    链接

    老赵 2009-08-28 12:02:00

    @billlo
    其实我每一篇文章都是工作中得出的想法……

  13. Ryan Gene
    *.*.*.*
    链接

    Ryan Gene 2009-08-28 12:07:00

    Jeffrey Zhao:
    @Ryan Gene
    所以JavaScript合你心意,想咋搞就咋搞,嘿嘿。



    呵呵,我觉得在一个类的时候,可能考虑不到那么周全,如果这个类给别人用,那多给人点空间应该是没错的

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

    韦恩卑鄙 2009-08-28 12:16:00

    作复杂设计的时候
    不可以不考虑是否公开扩展点

    java 建议大家不考虑
    c#建议大家考虑

    觉得c#的建议会让开始的工作略复杂些 但是后面受益
    java的倾向让开始的工作一气呵成 但是到后期没有一定功力是很容易闹出人命的。

  15. craboYang
    *.*.*.*
    链接

    craboYang 2009-08-28 12:16:00

    NHibernate目标为Domain对象,对于贫血模型的所有属性、成员,是否被扩展没有任何关系,此时的属性可以看成是成员。
    对于具有相关逻辑的属性,我则完全不同意Virtual,如同Add方法需要同步多个成员值。Nhibernate2.1还默认增加了是否Virtual的校验,否则就异常,我只好把它关了。
    是否Virtual应该取决与他的职责。

  16. oldrev
    *.*.*.*
    链接

    oldrev 2009-08-28 12:18:00

    NH 的 virtual 需求是为了实现 Dynamic Proxy,跟本文没有关系

  17. 老赵
    admin
    链接

    老赵 2009-08-28 12:22:00

    @oldrev
    嗯,只是一说。

    如果真是为了做dynamic proxy,我可以理解。
    但是NH的要求变态到这种地步:

    public User
    {
       public virtual int UserID{get;set;}
       public int Name {get;set;}
    }
    
    即使我的mapping只涉及到UserID,Name只是一个无关的辅助属性,为什么一定要求我把Name也设成virtual呢?

    此外,NH的很多功能是不需要dynamic proxy的,但是同样强制virtual,我也不太能接受。

  18. 老赵
    admin
    链接

    老赵 2009-08-28 12:23:00

    @craboYang
    原来NH可以关闭virtual校验?应该怎么做啊?

  19. 李永京
    *.*.*.*
    链接

    李永京 2009-08-28 12:24:00

    动态代理唯一的缺点就是不能代理not-virtual。。NHibernate用到了代理去实现延迟加载功能,所以Oren Eini都想一切都virtual咯

  20. 小太平洋
    *.*.*.*
    链接

    小太平洋 2009-08-28 12:27:00

    多了一种选择,何乐而不为

  21. andy.wu
    *.*.*.*
    链接

    andy.wu 2009-08-28 12:31:00

    这一类的问题,归根结底是编程思想的范畴,所以肯定会形成不同派别。

  22. 李永京
    *.*.*.*
    链接

    李永京 2009-08-28 12:31:00

    Jeffrey Zhao:
    @oldrev
    嗯,只是一说。

    如果真是为了做dynamic proxy,我可以理解。
    但是NH的要求变态到这种地步:

    public User
    {
       public virtual int UserID{get;set;}
       public int Name {get;set;}
    }
    
    即使我的mapping只涉及到UserID,Name只是一个无关的辅助属性,为什么一定要求我把Name也设成virtual呢?

    此外,NH的很多功能是不需要dynamic proxy的,但是同样强制virtual,我也不太能接受。


    老赵,帮忙看下http://davybrion.com/blog/2009/03/must-everything-be-virtual-with-nhibernate/

  23. 李永京
    *.*.*.*
    链接

    李永京 2009-08-28 12:32:00

    告诉我为什么啊

  24. something[未注册用户]
    *.*.*.*
    链接

    something[未注册用户] 2009-08-28 12:40:00

    我也更喜欢C#的这种做法(默认不是virtual的),我觉得约束太松了不好,比如那个IDisposable模式,Dispose()就调用vitual的Dispose(disposing)就好,但要是默认是virtual的,程序员一不小心就忘记给Dispose()标记上final了,这样就容易写出不好的代码了~~

    ZGG这系列文章很cool啊~~^_^

  25. Gray Zhang
    *.*.*.*
    链接

    Gray Zhang 2009-08-28 12:48:00

    NH的virtual确实是为了proxy做的,看看原因,其实这也是一种多余的担心,NH人设计者过滤了:
    class User {
    private string m_Name;
    public virtual string Name {
    get { return m_Name; }
    set { m_Name = value; }
    }
    public string LastName {
    get { return m_Name[0].ToString(); }
    }
    }
    我理解大家认为LastName是一个计算属性而不是映射属性,因此不应该需要virtual
    但是如果理解proxy是通过继承来做的,这里就有问题了
    class UserProxy : User {
    private string m_ProxiedName;
    public override string Name {
    get { return m_ProxiedName; }
    set { m_ProxiedName = value; }
    }
    }
    这个UserProxy重写了Name属性,由于private field不会到继承类中,因此UserProxy中是没有m_Name字段的
    这样一来,如果LastName属性不重写,那么永远报NullReferenceException,因为m_Name永远为null
    因此NH要求LastName也是virtual,NH会有策略吧LastName重写得与Name属性同步的(查找到LastName用了m_Name,从而改到m_ProxiedName去)
    很复杂吧,不过确实是有用的,为了避免一些莫名其妙的错误,当然其实对于永京这样的高级用户,肯定会写
    public string LastName {
    get { return Name[0].ToString(); }
    }
    这样其实确是是不用重写得……因此我才说NH这一步有些多余
    对了,这些代码都不严谨,仅示例用,别喷我……

  26. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-08-28 12:53:00

    我觉得决定什么成员可以扩展完全是设计者的工作,设计者认为可以扩展,就定义为virtual。有些设计者认为大多数成员都可以扩展,而逐个去写virtual有点麻烦,就建议所有成员默认为virtual;而有些设计者认为大多数成员都不可以扩展,如果所有成员默认为virtual则必须逐个去写final,这又很麻烦,所以反对这么做。
    所以,这应该是一个设计的倾向性问题。Java默认为virtual,是因为Java的设计者倾向于这种做法,C#默认为非virtual,是因为C#的设计者倾向于这种做法。而至于哪种做法更合理,完全是见仁见智,没有孰对孰错。
    我个人更倾向于从语义的角度去分析是否应该为virtual。比如,抽象类的方法实现,就应该尽量为虚方法。因为抽象类中的方法实现并不一定能够满足所有的子类,需要子类在需要的时候进行扩展。
    而老赵列举的ThreadSafeListBase和方法重载的例子,基本上属于模板方法模式的应用。如果模板方法中描述算法步骤的方法默认为virtual,那岂不是破坏了封装?

  27. ♂风车车.Net
    *.*.*.*
    链接

    ♂风车车.Net 2009-08-28 12:56:00

    我比较偏向 设计成virtual.

    其实,这个只是设计者所考虑角度不同而已.

    就像,开车的人和走路的人,开车的人总是抱怨路上的红绿灯太多,停的时间长; 走路的人,总是抱怨路上的红绿灯太少,过人行道的时间太短.

  28. craboYang
    *.*.*.*
    链接

    craboYang 2009-08-28 13:04:00

    我还以为我回帖是3楼呢,没想到还这么火。

    关闭Proxy校验:
    <entry key="use_proxy_validator" value="false"/>

    Domain内属性
    internal String Properties
    {
    get;set;
    }

    不影响运行

  29. 老赵
    admin
    链接

    老赵 2009-08-28 13:05:00

    @Gray Zhang
    NH会把LastName一并重写,那么强啊?
    这又涉及到一点了,我觉得NH对原有逻辑的破坏太严重,这也是我认为的缺点之一。
    例如,它为什么继承之后要自作主张地使用自己的私有变量呢?其实可以完全把代理类变成这样:

    public class UserProxy
    {
        public string Name
        {
            get { return base.Name; }
            set { base.Name = value; }
        }
    }
    
    这样完全可以实现同样的效果,并保证定义在基类属性中的逻辑不会被破坏。
    好多都是我想讨论的,看来我应该赶快写掉呢,呵呵。

  30. Gray Zhang
    *.*.*.*
    链接

    Gray Zhang 2009-08-28 13:07:00

    @Jeffrey Zhao
    这确实是一个问题,NH的侵入性不小,但是你也得承认,.NET领域里,侵入性比NH还小人ORM快绝种了……看看Entity Framework(非4.0版本)吧

  31. 老赵
    admin
    链接

    老赵 2009-08-28 13:09:00

    @Gray Zhang
    所以就像我之前说的,我找不到令我满意的ORM框架,“矮子里拔长子”,所以我选择了NH,呵呵。

  32. Gray Zhang
    *.*.*.*
    链接

    Gray Zhang 2009-08-28 13:12:00

    @Jeffrey Zhao
    彼此彼此,虽然说我现在的公司是没有ORM,IoC之类的概念的= =不过自己还是得学习的,很期待entity framework 4.0

  33. 潜水冠军[未注册用户]
    *.*.*.*
    链接

    潜水冠军[未注册用户] 2009-08-28 13:15:00

    老赵是如何访问blogspot的?

  34. a12345[未注册用户]
    *.*.*.*
    链接

    a12345[未注册用户] 2009-08-28 13:21:00

    virtual 常用于实现多态,只有在运行时才知道调用的是哪个子类的实现。因此效率会降低,而且还需要分配虚表,内存占用也会增加;对于那些不会用于继承的类来说不要把函数声明成 virtual的

  35. 李永京
    *.*.*.*
    链接

    李永京 2009-08-28 13:23:00

    @Gray Zhang
    我不是高级用户啊,呵呵

  36. 老赵
    admin
    链接

    老赵 2009-08-28 13:32:00

    a12345:virtual 常用于实现多态,只有在运行时才知道调用的是哪个子类的实现。因此效率会降低,而且还需要分配虚表,内存占用也会增加;对于那些不会用于继承的类来说不要把函数声明成 virtual的


    我认为就不必从这方面去考虑了……

  37. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-08-28 13:40:00

    @Jeffrey Zhao

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


    用Code Contract能把约束加在接口上,所以如果开始用Code Contract库或者是开始用.NET 4.0的话就能做这方面的处理。
    ……只是别人实现的时候没有默认实现来满足那些约束也会很烦。

    我是倾向于默认非virtual的。Ruby风格的对程序员的信任有时想想还是挺可怕的,能monkey-patch的能力比能virtual还要更进一步,但Ruby社区似乎仍很热衷这么做?这种开放的心态放在C#里感觉就怪怪的,呵呵

  38. Yin.Pu@CQUSoft
    *.*.*.*
    链接

    Yin.Pu@CQUSoft 2009-08-28 13:41:00

    我自己在设计的时候,比较偏向于把方法或属性设计成virtual的,除非我非常明确某个方法或属性不会被扩展,这种情况通常都是内部的一些辅助方法。
    另外,在设计的时候可能需要实现某种机制,在这个种时候我认为要严格控制扩展点,以防过多的开放破坏现有的机制,降低可控性,反倒使开发变得复杂。

  39. a12345[未注册用户]
    *.*.*.*
    链接

    a12345[未注册用户] 2009-08-28 13:42:00

    Jeffrey Zhao:

    a12345:virtual 常用于实现多态,只有在运行时才知道调用的是哪个子类的实现。因此效率会降低,而且还需要分配虚表,内存占用也会增加;对于那些不会用于继承的类来说不要把函数声明成 virtual的


    我认为就不必从这方面去考虑了……


    呵呵,纯粹考虑性能、内存这些确实意义不大,理解virtual的本质就好了

  40. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2009-08-28 14:03:00

    virtual的语义毫无疑问是清晰的有意义的,我没有细看那些大牛关于“应该全都默认virtual”的理由,只表达我的观点:

    1. 前面有TX说了,NH的virtual是为了Dynamic Proxy,所以,从NH的角度来说“应该全都默认virtual”,绝对没有道理。

    2. Dynamic Proxy不等同于AOP,JAVA得益于“全都virtual”,结果基于Dynamic Proxy的AOP在JAVA世界大行其道,也渐渐影响.NET世界,很多人本质上是出于对AOP的渴望,认为.NET应该“全都virtual”,这也是不合理的。virtual本身就不是为AOP而设计,因为“全都virtual”而使得Dynamic Proxy流行,也只是一个意外。事实上Dynamic Proxy也仅仅是一种其实并不怎么优雅的AOP实现方式。我们需要的是真正从底层支持AOP的语言或编译器,那样NH也自然不会那么恶心的要求“全都virtual”了,他要的其实就是AOP,而且我们又能保持virtual该有的语义,那才是正途。

  41. 阿龍
    *.*.*.*
    链接

    阿龍 2009-08-28 14:34:00

    不错,严重同意老赵观点。一门编程语言,就是已经设计好的一种编码方式。要是什么都可以扩展,应该用C语言,或者汇编自己慢慢编去。哈哈。

  42. Nick Wang (懒人王)
    *.*.*.*
    链接

    Nick Wang (懒人王) 2009-08-28 14:39:00

    我从前认为框架应该在合理的范围内对他人的行为做出限制,也就是限制扩展点和扩展方式;但是现在越来越觉得限制了扩展,也就限制了框架发展的可能性,束缚了开发人员的手脚,也很难适应未来的变化。

    ASP.NET MVC的扩展点就很多,才有可能在其上建立完全不同的解决方案。

    至于“错误的扩展”,虽然是一个问题,但是用刀来切菜还是用刀来杀人,永远都是使用人的责任,而不是刀的责任。

  43. craboYang
    *.*.*.*
    链接

    craboYang 2009-08-28 14:44:00

    Teddy's Knowledge Base:
    virtual的语义毫无疑问是清晰的有意义的,我没有细看那些大牛关于“应该全都默认virtual”的理由,只表达我的观点:


    不太同意啊:

    1. 前面我说了, NH可以use_proxy_validator=false,一个类可以部分属性Proxy,部分不Proxy

    2.Transparent Proxy不等同于AOP,“需要的是真正从底层支持AOP的语言或编译器”, 我们不需要在编译器级别AOP。 NH Transparent Proxy只是为了对属性get,set, Spring AOP 更多是为了绑架Method级拦截、注入。

    我觉得.NET 默认非Virtual是个非常好的封闭-开放规则。如果在我不想开放的前提下,居然被编译器自动Proxy了, 我完全无法控制他是否应该Proxy, 那本身也是非常可怕的事情。 .NET 目前的Transparent Proxy和AOP 我认为才是最纯正的。

    理解可能偏颇了, 不过Spring.NET 我0.6版即开始用到现在,官方文档我拜读了几十次了,应该还是了解Proxy&AOP

  44. 老赵
    admin
    链接

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

    @Nick Wang (懒人王)
    扩展点“多”也是设计出来,其实C#和Java都是可以选择的,只是默认情况不同。
    至于菜刀问题……虽然使用者有责任,但是如果刀子没有做好,容易误伤使用者的话,要使用者加倍小心的话,也不够理想啊。

  45. HOH
    *.*.*.*
    链接

    HOH 2009-08-28 15:10:00

    还是应该根据实际情况,该提供扩展点的就提供,不该提供的就不要提供。扩展应该是可控的,不可控的扩展,个人认为是没有什么意义的。

  46. Nick Wang (懒人王)
    *.*.*.*
    链接

    Nick Wang (懒人王) 2009-08-28 15:29:00

    @Jeffrey Zhao
    你说的没错,但是菜刀的使用者既有生手也有高手,做钝了也不好,做快了也不好,中庸一点更是不好,难啊。

    对于语言和通用框架这种用户众多的东西,本就是众口难调的。

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

    温景良(Jason) 2009-08-28 15:40:00

    如果全都是virtual的话,大家都可以重载,那不乱套了,还是按需使用好

  48. 老赵
    admin
    链接

    老赵 2009-08-28 15:55:00

    @温景良(Jason)
    不是重载,是override,重载是overload,呵呵。

  49. 老赵
    admin
    链接

    老赵 2009-08-28 15:56:00

    @Nick Wang (懒人王)
    所以我说其实Java和C#都可以给选择,本来两者都是合理的。
    但是我看Java框架基本上都不会主动使用final,说明倾向性给人的影响很大,人还是偷懒的……

  50. 资深那一年[未注册用户]
    *.*.*.*
    链接

    资深那一年[未注册用户] 2009-08-28 16:21:00

    个人倾向还是不需要都 virtual。也许是习惯问题。在设计的时候。会把可扩展的标成virtual,把不清楚的也设成 virtual。但是总会有些方法 是不希望 不允许 被别人override 的。

  51. 骨头
    *.*.*.*
    链接

    骨头 2009-08-28 16:46:00

    觉得还是少开放几个的好。真高产

  52. 中华小鹰
    *.*.*.*
    链接

    中华小鹰 2009-08-28 17:36:00

    我觉得类的设计者不仅要考虑该类能进行怎么养的扩展,更要考虑该类不能进行怎么养的扩展。如果把所有扩展点都开放,那对该类的使用者来说,就提高了使用该类的门槛,我觉得这是一种不负责任的做法。因此,我不赞同默认Virtual的做法。

  53. 小鹰二号[未注册用户]
    *.*.*.*
    链接

    小鹰二号[未注册用户] 2009-08-28 17:59:00

    老赵啥时候分析下OXITE这个项目~~~

  54. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-08-28 21:26:00

    这么说吧,Java的很多设计理念实际上是很糟糕的,或者说太过于追求某种形式。这与Java诞生的年代不无关系,老实说Java更像是一个概念产品,而C#作为后来者,在语法上显得更为成熟。Java更倾向于OO解决所有的问题,但显然务实的C#则更合我的胃口。无论是委托还是其他。。。。

  55. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-08-28 21:56:00

    是否可扩展是由设计者决定的
    要不要扩展是由消费者决定的

    所以不能默认全是virtual的

  56. Colin Han
    *.*.*.*
    链接

    Colin Han 2009-08-28 22:49:00

    RednaxelaFX:
    @Jeffrey Zhao

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


    用Code Contract能把约束加在接口上,所以如果开始用Code Contract库或者是开始用.NET 4.0的话就能做这方面的处理。
    ……只是别人实现的时候没有默认实现来满足那些约束也会很烦。

    我是倾向于默认非virtual的。Ruby风格的对程序员的信任有时想想还是挺可怕的,能monkey-patch的能力比能virtual还要更进一步,但Ruby社区似乎仍很热衷这么做?这种开放的心态放在C#里感觉就怪怪的,呵呵



    我不知道别的平台下(比如:Ruby)是否有专业的商业中间件提供商(我是指那些非开源的)。.NET下,中间件提供商需要考虑很多升级兼容性的问题。因此,如果暴露太多的控制点,对于中间件开发商的升级会是一个灾难。

    而,Ruby/Python等平台,更多的是开源软件的天下。因此,可能大家对升级的兼容性方面的考虑较少。因此,提供更多的控制点自然更利于别人的使用。

    另一方面,我的感觉,Ruby/Python等平台下的Geek比较多,他们自然更喜欢能控制更多的东西。而.NET下,商业应用,有时候需要的是IT民工,其中很多人甚至连什么时候用HashTable,什么时候用List还搞不清楚呢。暴露太多的内容,自然会引来灾难。

  57. 老赵
    admin
    链接

    老赵 2009-08-28 23:06:00

    @Colin Han
    Ruby/Python这种动态语言,基本上什么都是开放的,是个东西就能override掉,所以能够有monkey patching这种东西出现。

  58. xiaowen[未注册用户]
    *.*.*.*
    链接

    xiaowen[未注册用户] 2009-08-30 16:25:00

    要想方便,肯定是全Virtual方便。
    但是OO的最基本的原则之一就是要遵守Liskov替换原则,也就是任何子类都能被当作它的父类使用。如果默认非Virtual,可以更容易地帮助子类写出更符合此原则的代码。

  59. 老赵
    admin
    链接

    老赵 2009-08-30 16:33:00

    @xiaowen
    你说的和Ward挺接近的啊。

  60. xiaowen[未注册用户]
    *.*.*.*
    链接

    xiaowen[未注册用户] 2009-08-30 19:42:00

    @Jeffrey Zhao
    Ward那个网页我打不开,不知道他说了啥...

  61. heguo
    *.*.*.*
    链接

    heguo 2009-08-31 08:49:00

    Jeffrey Zhao:
    @幽梦新影
    我觉得NH的要求很无厘头,居然强制virtual,即使是我们不需要映射的辅助属性也要virtual,真的很恶心。
    这也是我打算写的话题。



    这可能是为了反射字段时比较方便.

  62. 老赵
    admin
    链接

    老赵 2009-08-31 09:38:00

    @heguo
    反射和virtual与否无关的阿。

  63. 老赵
    admin
    链接

    老赵 2009-08-31 09:39:00

    @xiaowen
    我在总结,可惜内容太多了……

  64. Franz
    *.*.*.*
    链接

    Franz 2009-08-31 13:27:00

    有个问题!
    构造器里最好别call virtual 方法,让继承此类的某些人用时可能会出现诡异的现象!

  65. qiaojie
    *.*.*.*
    链接

    qiaojie 2009-08-31 21:28:00

    默认不是virtual主要还是从性能角度考量的,多2次指针操作是其次,关键是virtual函数不能被内联,这个对性能还是有影响的,.net特别强调JIT的优化能力,所以语言的设计上自然也会引导用户在默认情况下写出高性能的代码,所以基本上C#写出来的代码,在性能上是非常有竞争力的。

  66. 老赵
    admin
    链接

    老赵 2009-08-31 22:26:00

    @qiaojie
    你说的这些我明白,但我不认为virtual与否是性能的关键,有太多东西的影响要远大过virtual了。
    所以,如果说非virtual是因为性能考虑,或是C#是因为非virtual而性能高……这个我不同意。

  67. qiaojie
    *.*.*.*
    链接

    qiaojie 2009-08-31 23:01:00

    @Jeffrey Zhao
    性能都是从小地方体现出来的。在某些情况下不能忽视virtual带来的额外开销,比方说C#中大量使用的property,本来因为内联的关系property的开销跟直接变量访问是一样的,在关键代码段里大量用property也没问题,但是如果是virtual的话,其性能要比直接变量访问差好多。

    在.net的设计原则中,性能肯定不是排第一的,但是.net的设计又是非常实用主义的,所以我们会看到很多设计是在尽可能的兼顾到性能,比方说为了在栈上分配对象而引入struct,甚至不惜牺牲安全性而引入指针。
    我们承认virtual和非virtual成员函数都有存在的价值,所以默认是不是virtual只是一个无足轻重的问题,就好比讨论C++的成员函数要不要默认是const的呢?那么这个时候如果要兼顾性能的话,显然应该默认为非virtual的。而java的设计哲学里性能是从来不被做为重要因素来考量的,所以默认为virtual也是可以理解的。

  68. 老赵
    admin
    链接

    老赵 2009-08-31 23:04:00

    @qiaojie
    你说的是讲的通的,不过的确是我第一次看到,呵呵。

  69. 老赵
    admin
    链接

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

  70. JimLiu
    *.*.*.*
    链接

    JimLiu 2009-09-09 20:31:00

    @Jeffrey Zhao
    对,NH要求virtual,对于mapped属性,我认了,毕竟人家要用proxy类,可是无关属性居然还要virtual,我觉得这就有点无厘头了。。

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我