Hello World
Spiga

再谈抽象类和接口

2009-08-19 12:15 by 老赵, 8750 visits

昨天我质疑了为什么定义RouteBase抽象类,而不是IRoute接口,我谈到对于一个“没有任何实现”的抽象类来说,开发人员应该使用接口。不过在后面的评论中,有朋友给了我启发,让我忽然想到更多的事情。晚上又再次翻了翻《Framework Design Guidelines》之后,打算再谈一些东西,把这个问题讨论地更加清楚一些。

上次谈了接口的好处,那么这次就主要谈一下接口的缺点。

API是会“升级”的。在一个类库的新版本中,往往会对旧有的API进行修改。作为一个公开的的API,应该做到“无痛升级”,也就是说API的演变不应该破坏现有的应用。而类(class)相对于接口(interface)的优势之一,便是在于类适合进行API的演变。

试想,在类库的1.0版本中定义了一个公开的接口,就比如IRoute吧,它其中有两个成员。于是乎开发人员在使用1.0版本的时候,就会实现这个IRoute接口:

public class MyRoute : IRoute
{
    public RouteData GetRouteData(HttpContextBase httpContext) { ... }

    public VirtualPathData GetVirtualPath(
        RequestContext requestContext, RouteValueDictionary values) { ... }
}

于是到了2.0版本中,开发人员发现,IRoute接口不够用,需要加入新的成员——但是不行,因为IRoute在1.0中已经公开了,这意味着如果向IRoute中添加新的成员,就可能会破坏外部已经使用IRoute接口的实现(如上面的MyRoute)。因此,一旦公开接口发布之后,它就不能被修改了。进而,这一点又可以引出了接口的一个设计准则:接口的职责应该尤其单一。例如.NET框架中的IComparable,IEnumerable等接口。因为如果您设计了一个接口,其中包含了许多成员,那么在新版本中这个接口则更有可能需要补充新的功能……但是接口又不可以修改,这该怎么办呢?

的确是进退两难的情况。在《Framework Design Guildlines》里提出了一些没办法时的办法,但书中也不得坦诚道,这些做法都不是好办法。事实上真没有什么好办法。

如果是“类”的话,问题就相对好办多了,因为我们可以向类中添加新的成员——只要这个新成员不是abstract的,就不会破坏外部已经出现的依赖。不过加上之后,API设计是否合理,语义是否清晰,就是另一回事情了。API设计不仅仅是技术活,每个举动都是需要推敲的。因为一旦发布,就没法删除了。

因此在这里再次感谢那位匿名朋友在评论中的提醒,他指出RouteBase可能是为了“预留”而实现成抽象类的。

说起语义和协议,可能就会涉及到接口的另一个特点,或者说也是个“缺陷”,那就是协议并不明确。接口定义了成员,也就是限制了实现这个接口的“外观”,但是对于“内在”是做不了任何限制的。例如,我们可以让一个方法抛出NotImplementedException,这便是“外强中干”的典型。还有一种情况,就比如IList<T>接口:

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

    ...
}

IList<T>接口只是限制了接口的外部表现:一个接受T类型的Add方法,还有表示元素数量的Count。但是在协议层面上,我们是无法限制这两个成员的关系的。例如,Add方法调用过后,Count肯定会增加1吗?此外还有,Add方法是不是线程安全的?仅通过接口都是不得而知的。

如果是抽象类,我们可以实现的东西就多了。例如,我们可以实现这样的一个抽象类:

public abstract class ThreadSafeListBase<T>
{
    private ReaderWriterLockSlim m_rwLock = new ReaderWriterLockSlim();

    public int Count { get; private set; }

    public void Add(T item)
    {
        this.m_rwLock.EnterWriteLock();
        try
        {
            this.Count++;
            this.AddCore(item);
        }
        finally
        {
            this.m_rwLock.ExitWriteLock();
        }
    }

    protected abstract void AddCore(T item);

    ...
}

这么做,就在一定程度上对实现内容进行了约束。但是很明显约束是不可能彻底的(如AddCore也可能抛出NotImplementedException),最彻底的约束便是一个sealed class——但这个很明显,就已经不是抽象了。

接口的确有优势(可以让类来实现多个接口,且struct实现接口不能继承一个类),但是在这篇文章中我们也看到接口也是有一定缺陷的。设计需要平衡,没有什么东西是最好的,也没有什么东西总是最合适的。

不过比较奇怪的是,有(不止一个)朋友回复说,使用抽象类是为了使用扩展方法。我从来没有看到过某个资料说接口和扩展方法有任何冲突,事实上我们也可以为接口定义扩展方法。由于一个类可以实现多个接口,而接口又可以实现扩展方法,这似乎也又有了“多继承”的意味。

Creative Commons License

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

Add your comment

69 条回复

  1. ouzi2[未注册用户]
    *.*.*.*
    链接

    ouzi2[未注册用户] 2009-08-19 12:20:00

    老赵的沙发没人抢

  2. pzhxd[未注册用户]
    *.*.*.*
    链接

    pzhxd[未注册用户] 2009-08-19 12:27:00

    回复中所谓的扩展方法含义可能是指为抽象类新加方法来实现扩展,而不是指c#3语言特性中的扩展方法吧.

  3. Jerry Qian
    *.*.*.*
    链接

    Jerry Qian 2009-08-19 12:27:00

    先顶再看

  4. Jeff Wong
    *.*.*.*
    链接

    Jeff Wong 2009-08-19 12:35:00

    mark,老赵的文章终于第一次一遍就看懂意思了。

  5. 菜鸟2009[未注册用户]
    *.*.*.*
    链接

    菜鸟2009[未注册用户] 2009-08-19 12:38:00

    为接口定义扩展方法。

    这个还需要讨论吗?
    这些人难道没用过linq吗?

  6. 老赵
    admin
    链接

    老赵 2009-08-19 12:39:00

    Jeff Wong:mark,老赵的文章终于第一次一遍就看懂意思了。


    嗯,我接下去可能会写更多简单一点的,各方面带有个人想法的东西。

  7. 老赵
    admin
    链接

    老赵 2009-08-19 12:40:00

    pzhxd:回复中所谓的扩展方法含义可能是指为抽象类新加方法来实现扩展,而不是指c#3语言特性中的扩展方法吧.


    我不知道啊,有个朋友的原话是“使用抽象类的原因,是不是可以用C#3.0的扩展方法”。

  8. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-08-19 12:45:00

    啊,没看上一帖……

    话说我想起Java的AWT的API设计,其中有很多类似这样的接口:

    public interface MouseListener extends EventListener {
        void mouseClicked(MouseEvent e);
        void mouseEntered(MouseEvent e);
        void mouseExited(MouseEvent e);
        void mousePressed(MouseEvent e);
        void mouseReleased(MouseEvent e);
    }
    

    如果要监听某个鼠标事件,例如说我只要监听clicked,那么实现这个接口就需要实现所有5个方法,包括那4个我不关注的。

    然后AWT里还有另外一些抽象类:
    public abstract class MouseAdapter
        implements MouseListener, MouseWheelListener, MouseMotionListener {
        public void mouseClicked(MouseEvent e) { }
        public void mouseEntered(MouseEvent e) { }
        public void mouseExited(MouseEvent e) { }
        public void mousePressed(MouseEvent e) { }
        public void mouseReleased(MouseEvent e) { }
        // ...
    }
    

    它只是实现了些接口,内容全部是空的实现。继承这个抽象类就可以少写很多空方法,省点事。

    老赵是怎么看这种设计的?这个例子中的MouseListener的职责是否不够单一?

  9. Jeff Wong
    *.*.*.*
    链接

    Jeff Wong 2009-08-19 12:49:00

    Jeffrey Zhao:

    Jeff Wong:mark,老赵的文章终于第一次一遍就看懂意思了。


    嗯,我接下去可能会写更多简单一点的,各方面带有个人想法的东西。


    我的印象中你的“带有个人想法的东西”通常都不简单,这会不会是一个悖论啊?收藏你很多文章,敢说完全领悟的也没有几篇。有的时候知识面太广泛而且深刻的话,新手只能膜拜大神一下,哈哈。

  10. 老赵
    admin
    链接

    老赵 2009-08-19 12:52:00

    @RednaxelaFX
    呵呵,又被你抢先了,其实我今天还打算写一篇文章谈一下类似的问题呢。就先在这里讨论一下吧。说实话,我不喜欢Java的这个设计,它的确职责不单一。
    我觉得Java也是意识到这个问题的,所以把Mouse相关事件和MouseMove相关事件也分开了,也算是一定程度上职责的差分。
    我还是喜欢.NET的事件机制,粒度更细,使用更方便。

  11. 老赵
    admin
    链接

    老赵 2009-08-19 12:54:00

    Jeff Wong:
    我的印象中你的“带有个人想法的东西”通常都不简单。


    可能没有说清楚,我的意思是一些小地方上拥有个人想法的东西。
    或者说,现在开始我打算想到一点写一点,而不是“积累”够了一下子来篇长的。

  12. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-08-19 13:06:00

    @Jeffrey Zhao
    呵呵,那正好作为引子了。坐等老赵关于这个问题的讨论。

    其实我之所以会马上联想到这种空的抽象类实现,是因为我的代码里就有这种东西。
    例如我为我的一组AST实现double-dispatch的Visitor模式,那么每个AST类上有Accept()方法这个没问题,而Visitor类则需要知道所有相关的具体类,为每个类设计一个Visit()方法。为了省事,我用脚本来生成抽象的基类AbstractVisitor,其中自动为每个AST具体类生成一个空的Visit()实现。然后我要针对某些节点写Visitor的时候只要继承这个抽象基类即可。
    这种设计又应该如何评价呢?我觉得Visitor模式本身对封装的破坏也是一点,不过还有没有什么别的好处和坏处之类的……

  13. 熊呜呜
    *.*.*.*
    链接

    熊呜呜 2009-08-19 13:14:00

    个人浅见如下:
    定义接口应当是对一个建模的概念的完整规格说明。例如:IEnumerable等

    如果无法对建模的概念进行明确的完整说明,则不应使用接口。

    抽象类的对象抽象概念与接口的差别,就是允许包换公共状态和行为的。

    采用RouteBase抽象类是为了将来把公共状态和行为提取出来,至于大家说的以便于以后的增加方法扩展我觉得倒是其次。

  14. 请问电子书[未注册用户]
    *.*.*.*
    链接

    请问电子书[未注册用户] 2009-08-19 13:26:00

    @Jeffrey Zhao
    Framework Design Guidelines 2nd的电子书在哪里可以下载!呵呵,顺便问一句难道你买的是国外正版的书籍?听说你常通过网上买国外正版书籍?呵呵.同时对你对技术的如此执着表示尊敬!

  15. lakie[未注册用户]
    *.*.*.*
    链接

    lakie[未注册用户] 2009-08-19 13:44:00

    如果是为了能更灵活的扩展api得方法,我觉得倒不是接口的缺陷,因为你仅仅为了扩展方法,你可以再另外定义一个接口来包容新加的方法啊,如果你使用抽象类来规避这个问题,那就不能标注为abstract,那还不如直接用类表示好了,这样才真正扩展一个方法没多少影响。
    而且恰恰使用抽象类会有这个问题,因为一个类只能继承一个父类或者抽象类,如果你要扩展方法,这个时候你必须要去改抽象类,这似乎违背某些优良的编程原则(不记得叫什么了,好像是写好的东西不要轻易去动它,避免程序重新编译或者测试什么的)。而如果用接口的话,你可以再定义一个接口,让一个类来继承。这样就不必动原来的接口,也不会影响另外继承了最初那个接口的类。

    记得以前看过敏捷开发里提到的两个原则,一个是接口分离,一个是依赖倒置,类似是为了在使用接口的时候,规避你说的这个问题。

  16. winzheng
    *.*.*.*
    链接

    winzheng 2009-08-19 13:53:00

    老赵的文章果然,精悍了很多,有点不过瘾了。

  17. 老赵
    admin
    链接

    老赵 2009-08-19 14:05:00

    @熊呜呜
    似乎还差点什么……我觉得IRoute比RouteBase合适,因为其实Route应该只是行为,而没有什么“状态”在里面。

  18. 老赵
    admin
    链接

    老赵 2009-08-19 14:05:00

    @请问电子书
    我看的是国外买的原版。

  19. 老赵
    admin
    链接

    老赵 2009-08-19 14:09:00

    @lakie
    我说的是接口的职责的扩充,如果是可以定义成其他接口,那么说明本不是一种职责啊,本来就不应该修改原有接口。
    修改抽象类不会引起重新编译,只要不添加抽象成员就可以了。Framework Design Guidelines里举了个Stream的例子,我觉得很合适。
    至于依赖倒置等等……我觉得和我现在说的问题关系不大,谈不上“规避”不规避的吧。
    还有我越来越认为,开发一个公开的API,和一套项目中自己的设计,方式是不一样的,因为公开API一旦发布再也不可以修改,所以会走更“保险”的路。

  20. 老赵
    admin
    链接

    老赵 2009-08-19 14:10:00

    winzheng:老赵的文章果然,精悍了很多,有点不过瘾了。


    我打算转为话唠风,希望可以获得更多反馈。
    我写博客的目的是交流,但是我发现好像慢慢变得我再写稿一样。

  21. 张少峰
    *.*.*.*
    链接

    张少峰 2009-08-19 14:14:00

    看到老赵发文,特来瞻仰~
    缺点和优点都是在一定的场景下才产生的。换个场景,优点也许就变成了缺点。抽象类是方便加新功能,但是加的功能也许并不是每一个子类都需要的。接口和抽象类,应该用哪个,怎么用,就要具体问题具体分析了~!~

  22. 老赵
    admin
    链接

    老赵 2009-08-19 14:19:00

    @RednaxelaFX
    其实我不怎么懂设计的……我就是谈谈怎么做“比较好”,例如有哪些好处,哪些坏处。现在这些东西有个名称叫做“实现模式”,看上去一下子不山寨了,嘿嘿。

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

    something[未注册用户] 2009-08-19 14:27:00

    @lakie
    我还是感觉接口和抽象类在于契约语义上的区别比较关键。您说的应该是开-闭原则吧,但我觉得这个原则并不是说你要是改了原先的代码就是“不好”,而是说你的设计好得足以让修改可以不用改原先的代码,但是人都不是神,程序都是可能会有点bug的,难道改个bug也要非得不直接改原代码,不重新编译原代码么?如果这样我觉得就是对OCP的过度理解。而在此同时,如果接口并不是非常的稳定,却将它定义为接口,那将来它一改,所有使用了你的类库的程序都不能运行了,而抽象类则可以,那为什么要用抽象类而不直接用类呢?因为抽象类不可以被实例化,在许多场景中,一个类是不应该被实例化的,可以在.net fx中找到许多这样的类,通常只能实例化它的子类,或调用它本身的某个Create什么的静态方法来实例化。

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

    something[未注册用户] 2009-08-19 14:28:00

    "进而,这一点又可以引出了接口的一个设计准则:接口的职责应该尤其单一。"
    -------------------
    非常感谢,这句话让我又加深了些理解^^

  25. 老翁
    *.*.*.*
    链接

    老翁 2009-08-19 14:53:00

    支持老赵写一些比较短的文章。短而精,观点明确,易于讨论、交流。

    在API“无痛演变”的问题上,多个职责单一的接口与一个抽象类构不成太明显的优劣对比吧?

  26. 阿龍
    *.*.*.*
    链接

    阿龍 2009-08-19 14:58:00

    Jeffrey Zhao:
    我看的是国外买的原版。


    你上次说你不看书来着,只是看看帮助和博客...骗人。。。。

  27. 佩服呀[未注册用户]
    *.*.*.*
    链接

    佩服呀[未注册用户] 2009-08-19 15:00:00

    @Jeffrey Zhao

    Jeffrey Zhao:
    @请问电子书
    我看的是国外买的原版。


    我想花大价钱买国外原版书籍的大概也只有老赵等寥寥几个人了,技术牛人好像不计较代价的,因为技术本身占据了他的一部分,唉,这就是差距。

  28. 老赵
    admin
    链接

    老赵 2009-08-19 15:01:00

    @阿龍
    怎么可能完全不看书,“思想”方面的东西肯定要看啊,看博客不也是看思想吗?
    我说我不看“纯教材”,就是教你C#,ASP.NET之类的书,还有“XX编程三百例”这种,这种东西我就看帮助来了解了。

  29. 老赵
    admin
    链接

    老赵 2009-08-19 15:04:00

    佩服呀:
    我想花大价钱买国外原版书籍的大概也只有老赵等寥寥几个人了,技术牛人好像不计较代价的,因为技术本身占据了他的一部分,唉,这就是差距。


    我是纯属个人喜好……这本书国内已经引进了啊。

  30. Jaxu
    *.*.*.*
    链接

    Jaxu 2009-08-19 15:12:00

    不错!老赵的文章还是很有看头的,分析得有理有据。

  31. 别爱上哥,哥只是个传说!
    *.*.*.*
    链接

    别爱上哥,哥只是个传说! 2009-08-19 15:21:00

    晕,,,老赵..改张图片吧,你那手手势,跟一个非主流的差不多,每次进来第一眼就看到,不爽啊..呵呵

  32. xland
    *.*.*.*
    链接

    xland 2009-08-19 15:32:00

    老赵 你的薄皮中“最新评论” 和 “我的情况”都在动
    影响看你的文章

  33. 老赵
    admin
    链接

    老赵 2009-08-19 15:32:00

    @别爱上哥,哥只是个传说!
    那个……我真没别的照片……

  34. 老赵
    admin
    链接

    老赵 2009-08-19 15:38:00

    @xland
    我晚上改一下吧,Hack一次不容易啊。

  35. 未注册用户[未注册用户]
    *.*.*.*
    链接

    未注册用户[未注册用户] 2009-08-19 15:49:00

    老赵是左撇子,鉴定完毕,以前看到一张很胖的照片,样子很憨。哈哈,就是那个”光荣的程序员“

  36. 老尼[未注册用户]
    *.*.*.*
    链接

    老尼[未注册用户] 2009-08-19 15:56:00

    终于看懂一次了!

  37. Grove.Chu
    *.*.*.*
    链接

    Grove.Chu 2009-08-19 16:20:00

    设计需要平衡,没有什么东西是最好的,也没有什么东西总是最合适的。
    顶!

  38. vczh[未注册用户]
    *.*.*.*
    链接

    vczh[未注册用户] 2009-08-19 16:34:00

    这让我想到了
    ID3DDevice7
    ID3DDevice8
    ID3DDevice9
    ID3DDevice10
    ID3DDevice11

  39. 潇笑
    *.*.*.*
    链接

    潇笑 2009-08-19 16:50:00

    --定义RouteBase抽象类,而不是IRoute接口
    其实这个猜想也不一定正确,API升级,基本上版本都变了,不太可能在一个成熟的版本里追加新的功能了

    -----------------
    试想,在类库的1.0版本中定义了一个公开的接口,就比如IRoute吧,它其中有两个成员。于是乎开发人员在使用1.0版本的时候,就会实现这个IRoute接口:

    public class MyRoute : IRoute
    {
    public RouteData GetRouteData(HttpContextBase httpContext) { ... }

    public VirtualPathData GetVirtualPath(
    RequestContext requestContext, RouteValueDictionary values) { ... }
    }
    -------------
    需要添加功能到了2.0里可以这样么
    public class MyRoute : IRoute,IOtherInterface
    {
    ....
    }

    这对之前的引用是没有影响的了,数据类还不都是实现了大量的接口么
    可能是对实现RouteBase的类要求比较单一的功能,只需要否则相关的功能就OK了  

  40. 老赵
    admin
    链接

    老赵 2009-08-19 16:56:00

    @潇笑
    现在说的情况是“无痛升级”,也就是不重新编译外部的程序集,你这不是编译了吗?

    所以情况应该是这样的,外部系统基于1.0已经写好了一个MyRoute : IRoute类。
    而1.0版的框架中有一个SomeMethod方法使用了IRoute接口,这个方法的签名也是不许变的。
    因此,如果升级到2.0之后,扩展功能是不是需要实现一个新的INewRoute接口呢?是不是每次都要这样才能利用起新功能:
    void SomeMethod(IRoute route)
    {
      var newRoute = route as INewRoute;
      if (newRoute != null) { ... }
    }

    如果1.0的类库中有n个原本利用到IRoute接口的方法呢?就要不断用这种麻烦而丑陋的方法了。

  41. 潇笑
    *.*.*.*
    链接

    潇笑 2009-08-19 17:21:00

    额,有点道理,基类更适合于向上扩充.
    .net类库里有大量abstract类续承自abstract类,在续乘承自接口的类

  42. CHwANG
    *.*.*.*
    链接

    CHwANG 2009-08-19 18:16:00

  43. 老赵
    admin
    链接

    老赵 2009-08-19 18:48:00

    @KUI
    没理解你的意思,能否具体谈谈?

  44. KUI[未注册用户]
    *.*.*.*
    链接

    KUI[未注册用户] 2009-08-19 18:48:00

    感觉接口就像是一个横向的契约,超类像是一个纵向的契约

  45. Colin Han
    *.*.*.*
    链接

    Colin Han 2009-08-19 20:05:00

    Jeffrey Zhao:

    佩服呀:
    我想花大价钱买国外原版书籍的大概也只有老赵等寥寥几个人了,技术牛人好像不计较代价的,因为技术本身占据了他的一部分,唉,这就是差距。


    我是纯属个人喜好……这本书国内已经引进了啊。


    MSDN中就已经包含了这本书,只是版本比较老。如果你想看,又囊中羞涩,可以看MSDN。
    我爱看MSDN。不过,中文的就不要看了,机器翻译的,拿来对照一下还行,直接看会被带到沟里面去的。

  46. Colin Han
    *.*.*.*
    链接

    Colin Han 2009-08-19 20:10:00

    Jeffrey Zhao:
    @lakie
    我说的是接口的职责的扩充,如果是可以定义成其他接口,那么说明本不是一种职责啊,本来就不应该修改原有接口。
    修改抽象类不会引起重新编译,只要不添加抽象成员就可以了。Framework Design Guidelines里举了个Stream的例子,我觉得很合适。
    至于依赖倒置等等……我觉得和我现在说的问题关系不大,谈不上“规避”不规避的吧。
    还有我越来越认为,开发一个公开的API,和一套项目中自己的设计,方式是不一样的,因为公开API一旦发布再也不可以修改,所以会走更“保险”的路。


    呵呵,我们做控件的,就存在这样的问题,一个不好的接口暴露会让我们悔恨好几个版本的。(一个版本就按一年算吧)
    一个Interface暴露给用户后,基本上就完全不能改了。因此,我们很少暴露Interface,好在是.NET事件机制很争气。否则还真的会哭的。如果真的需要暴露接口的时候,我还是更愿意将接口划分为更细的粒度。毕竟接口的多继承是没有成本的。只是写出的代码会稍微有点恶。需要大量的类型转换。

  47. HBZ[未注册用户]
    *.*.*.*
    链接

    HBZ[未注册用户] 2009-08-19 20:59:00

    嘿嘿,继续虚构昨晚那个故事,假设设计之初使用了接口,
    某年某月某日 MS 被百度收购了,.net8.0如期发布了,李彦宏(or一男)说“我们要给ASP.NET MVC的Route内加上 GetKuangJiSuanXXXX()这么一个方法”,scottgu哭丧着脸说:“sorry啊,我的老大,加不进去了,因为很多年前我们用了接口...”现在加进去肯定会被博客园那帮大仙们骂死的...
    (*^__^*)

  48. andy.wu
    *.*.*.*
    链接

    andy.wu 2009-08-19 21:56:00

    Jeffrey Zhao:

    winzheng:老赵的文章果然,精悍了很多,有点不过瘾了。


    我打算转为话唠风,希望可以获得更多反馈。
    我写博客的目的是交流,但是我发现好像慢慢变得我再写稿一样。



    看来老赵已开始受ayende的影响了,:)。

    我还是很认同ayende的风格,重在交流。有时候我看到ayende很无聊的post,会有一大帮人在那回复,看看也很有味道。

  49. 老赵
    admin
    链接

    老赵 2009-08-19 22:15:00

    @andy.wu
    oyeah,理解万岁。

  50. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-08-19 23:17:00

    从OOD语义角度来说,滥用接口更不可取,我倒是认为最后IController接口都会被废弃。

    对于Context而言,IContext不能说是一个好的设计,这意味着Context可以是别的东西。而Context最好应该就是Context。任何一个类型都应该要有主要职责,这个主要职责应该使用类型来描述(在单根继承的体系中)。所以IController和IContext的接口都不应该出现。

    当一个类型设计成为:

    public class XXX : IContext, IXXXX

    的时候,会让人搞不清它的主要职责是什么,而设计成:

    public class XXX : ContextBase, IXXXX
    则更合理。

    即使这个ContextBase直接继承IContext啥事儿也不干,这也是语义更完备的表现。


    合理的限制自己的行为,就是在另一方面更完备的表现。一个类继承于ContextBase,意味着他的主业就是Context,因为在单根继承的语言体系中,它是不能有另一份主业的。

  51. 老赵
    admin
    链接

    老赵 2009-08-19 23:50:00

    @Ivony...
    IController肯定不会被抛弃,任何东西一旦public就不会抛弃了。
    我认为所谓语义的完备不应该通过这种“文字表现形式”来体现,而是应该真正体现在设计上的。
    也就是说,如果职责应该分离,则分离,接口也一样,这和通过抽象类来体现语义,体现“职责”,是没有什么关系的。

  52. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-08-19 23:59:00

    对于Context而言,IContext不能说是一个好的设计,这意味着Context可以是别的东西。

    语义的关键在于这里,当一个类只是实现一个IContext接口时,其实在暗示,这个类型只是兼职Context。

    换言之,一个类型可能在是一个Context(实现IContext)的同时,兼职另一份或者是主业的工作(譬如说配置容器)

    或者说,IContext可能导致这样的设计:

    public class Configuration : XElement, IContext

    或许这不是一个很好的例子。

    当然这可以说是一个很糟糕的设计,那么如果所有类似的设计都是糟糕的时候,IContext前面的I就是毫无意义的。

    恕我表达能力有限。

  53. 老赵
    admin
    链接

    老赵 2009-08-20 00:06:00

    @Ivony...
    我明白你的意思了。:)
    使用“类”的理由有不少,只是我还是不太认同为了这种“语义”而去“强行”做出限制。

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

    Ivony... 2009-08-20 00:18:00

    从语义上来说,接口的意义在于你可以利用它来给一个类型添加附加的语义,如果当这种语义永远不可能是一种附加的时候,那么应该设计为类型,保留接口应仅是为了功能上的抽象需要,如果这种抽象都不需要(比如说HttpContext,至少在设计的时候看不到有抽象的必要,用户不应当自己创建HttpContext),就没有存在的意义了(或许这导致了后来的HttpContextBase)。

    所以我并不认为HttpContext没有留下一个接口而导致要HttpContextBase这样可能看起来不是很舒服的hack。但我还是觉得这种设计对于原有的ASP.NET体系而言是完备的。

  55. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-08-20 00:30:00

    Jeffrey Zhao:
    @Ivony...
    我明白你的意思了。:)
    使用“类”的理由有不少,只是我还是不太认同为了这种“语义”而去“强行”做出限制。



    正如同我们要用一些小技巧去访问internal的成员一样,任何东西都是有利有弊的。微软大可以public这些东西,然后我们就可以堂而皇之的使用了,有时候需要权衡。限制是为了避免混乱,但有时候也会把一些善意的企图挡在门外。

    半夜讨论问题容易脑子乱,如果说错了大家手下留情。

  56. 斯克迪亚
    *.*.*.*
    链接

    斯克迪亚 2009-08-20 02:01:00

    受益不少

  57. Arthraim
    *.*.*.*
    链接

    Arthraim 2009-08-20 07:27:00

    这才发现接口升级是痛苦的……
    还是IRoute1 IRoute2吧,让调用的同学自己去改算了……

  58. 好俊的功夫啊
    *.*.*.*
    链接

    好俊的功夫啊 2009-08-20 08:30:00

    这意味着如果向IRoute中添加新的成员,就可能会破坏外部已经使用IRoute接口的实现(如上面的MyRoute)

    不什么说在接口中增加新成员,就会破坏原来的实现呢!请教

  59. 老赵
    admin
    链接

    老赵 2009-08-20 08:55:00

    好俊的功夫啊:
    不什么说在接口中增加新成员,就会破坏原来的实现呢!请教


    因为外部程序集已经使用了原来的IRoute,如果新版本的IRoute中增加了一个成员,那么根据旧版本实现的IRoute就少实现一个成员了,就破坏了。

  60. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-08-20 09:23:00

    对于抽象类和接口的选择,能否单纯的根据语义上的IS A和HAS A来决定呢?

  61. 老赵
    admin
    链接

    老赵 2009-08-20 09:41:00

    @麒麟.NET
    这就完全不对了,Has A是包含关系,是指“组合”,不是继承或实现。

    MyComponent : Componet, IComparable

    表示的是:MyComponent is a component and also a comparable object.

  62. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-08-20 10:02:00

    ……晕了,写错了,是CAN DO

  63. AppleSeeker
    *.*.*.*
    链接

    AppleSeeker 2009-08-20 10:05:00

    之所有很多人混淆因为,很多人不用接口实现。而只会用抽象类实现。

  64. Sail
    *.*.*.*
    链接

    Sail 2009-08-20 10:07:00

    我靠,还不让用ie6。ie8太麻烦。

  65. heihei123[未注册用户]
    *.*.*.*
    链接

    heihei123[未注册用户] 2009-08-20 10:08:00

    又胖了

  66. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-08-20 14:52:00

    麒麟.NET:……晕了,写错了,是CAN DO




    接口的确是可以理解为can do。
    也可以认为,类(无所谓抽象与否)表达的是be的逻辑,接口表达的是like的逻辑。

    因为相对于接口而言,类可以有更多的制约,类的继承是一个抽象到具象的过程,从某种意义上来说是从开放到封闭的过程。接口则不然。

    一个IRoute类型的对象,从语义上来说,这是一个like Route的对象,也就是说它看起来像Route。至于是不是,这个不确定。这也就是我说的如果有功能上的抽象需要,保留接口。保留接口的最大意义在于你可以让一个东西像Route,简单的说,你可以自己做一个URL传递体系,比如说UriData类,实现的机制与Route是完全不同的。但是ASP.NET MVC却只认识Route的数据,这个时候你可能就需要使得这个UriData看起来像Route,很显然的在这种场景下,实现接口而不是继承类就是一个非常棒的设计。从语义上是完美的,实现上也就不会遇到太多问题。

    老实说我也只是隐隐约约支离破碎的能够理解这里面的一些东西,要完整的说清楚,我的能力显然还没有达到。

  67. chenleinet
    *.*.*.*
    链接

    chenleinet 2009-08-22 21:25:00

    我发觉,评论有时候还能有助于理解文章的含义,而且评论有时候,比文章还要有意思

  68. 老赵
    admin
    链接

    老赵 2009-08-22 21:58:00

    @chenleinet
    所以我说,一定要讨论。

  69. ddrrr[未注册用户]
    *.*.*.*
    链接

    ddrrr[未注册用户] 2009-09-23 15:13:00

    @chenleinet

    潇笑:
    额,有点道理,基类更适合于向上扩充.
    .net类库里有大量abstract类续承自abstract类,在续乘承自接口的类


    chenleinet:我发觉,评论有时候还能有助于理解文章的含义,而且评论有时候,比文章还要有意思


发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我