Hello World
Spiga

ASP.NET Routing中最令人摸不着头脑的设计

2009-08-18 19:47 by 老赵, 7145 visits

您觉得ASP.NET Routing中最令人摸不着头脑的设计是什么?我认为是RouteBase类:

public abstract class RouteBase
{
    protected RouteBase() { }
    public abstract RouteData GetRouteData(HttpContextBase httpContext);
    public abstract VirtualPathData GetVirtualPath(
        RequestContext requestContext,
        RouteValueDictionary values);
}

它为什么是一个没有任何实现的抽象类,而不是一个接口(如下)?

public interface IRoute
{
    RouteData GetRouteData(HttpContextBase httpContext);
    VirtualPathData GetVirtualPath(
        RequestContext requestContext,
        RouteValueDictionary values);
}

这样做难道不更漂亮一些吗?这样代码中都可以使用IRoute类型,避免RouteBase这种令人反感的命名出现(个人感觉,不知道有没有同意的群众)。退一步说,命名上的“美感”是小事……但是抽象类在.NET平台中就产生了一个非常严重的限制:一个类无法继承多个基类。因此,在.NET平台上总是更倾向于使用接口,而不是抽象类。

但是接口里不可以有任何实现,那么可复用的功能又放在哪里比较合适呢?《Framework Design Guildlines》告诉我们:在一个类库中,最好为接口定义一个默认实现,这样也是开发人员进行“扩展”的一个“参考”。也就是说,如果真有什么需要复用的实现,我们完全可以这么做:

public abstract class RouteBase : IRoute
{ 
    // reusable implementations
}

public class Route : RouteBase
{
    // concrete implementations
}

事实上,.NET平台上有许多类库也遵循了这个做法。一个典型的做法便是ASP.NET AJAX框架的Extender模型:

public interface IExtenderControl { }

public abstract class ExtenderControl : Control, IExtenderControl { }

甚至在ASP.NET AJAX Control Tookit项目中,还有更进一步的扩展:

public abstract class ExtenderControlBase : ExtenderControl { }

public class AnimationExtenderControlBase : ExtenderControlBase { }

public class AutoCompleteExtender : AnimationExtenderControlBase { }

看来微软在项目团队内部推广《Framework Design Guidelines》还不够彻底。

在.NET平台下,一个没有任何实现的,纯粹的抽象类可谓有百害而无一利1。我很怀疑写这段代码的人刚从C++切换到C#——但是ASP.NET Routing中其实也有接口(如IRouteConstraint),为什么作者自己没有意识到,也没有人提出不同意见呢?微软开发团队应该有着严格的Code Review过程,怎么会让这样的代码正式发布?要知道一个接口一旦公开,就不可以删除了。也就是说,微软很难弥补这个错误。

如果是方法名不好,或者职责有些不明确,这样还可以在旧方法上添加ObsoleteAttribute(这样编译器便会提示用户这个方法已经过期),并且将旧方法的调用委托给新的实现。例如:

public abstract class CodeDomProvider : Component
{
    [Obsolete(
        "Callers should not use the ICodeCompiler interface and should
         instead use the methods directly on the CodeDomProvider class.
         Those inheriting from CodeDomProvider must still implement this
         interface, and should exclude this warning or also obsolete this
         method.")]
    public abstract ICodeCompiler CreateCompiler();

    [Obsolete(
        "Callers should not use the ICodeParser interface and should
         instead use the methods directly on the CodeDomProvider class.
         Those inheriting from CodeDomProvider must still implement this
         interface, and should exclude this warning or also obsolete this
         method.")]
    public virtual ICodeParser CreateParser();

    ...
}

可是,现在的问题是一个“类”,而这个类已经无处不在了,例如在RouteData中有一个属性Route,它便是RouteBase类型——如果将其修改为IRoute接口,那么至少也需要项目重新编译之后才能够“升级”。而作为一个公开类库,尤其是.NET这种成熟框架来说,应该做到“无痛”才对。

这次微软真搞笑了。

 

注1:这自然是夸张的做法,抽象类并非没有优势,请关注《再谈抽象类和接口》一文。

Creative Commons License

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

Add your comment

59 条回复

  1. 横刀天笑
    *.*.*.*
    链接

    横刀天笑 2009-08-18 19:47:00

    话说,你今儿真是高产啊

  2. 横刀天笑
    *.*.*.*
    链接

    横刀天笑 2009-08-18 19:51:00

    也许这是微软故意的,故意不让你还继承别的类(我瞎说的,我都不知道Routing为何物)

  3. 老赵
    admin
    链接

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

    @横刀天笑
    话说,我打算以后写的东西小一点,多写一点。
    以前把这里当“博客园”,为首页而发,篇篇很长,三五千字,很累。
    以后我打算把这里当自己的博客,想到一些细节或技术方面的体会就写一篇——最多不放到首页上去,
    所以欢迎大家通过RSS订阅(http://www.cnblogs.com/JeffreyZhao/rss)。
    http://ayende.com/这种猛人,平均一个月2、30篇博客的,这才叫博客嘛。

  4. 老赵
    admin
    链接

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

    横刀天笑:也许这是微软故意的,故意不让你还继承别的类(我瞎说的,我都不知道Routing为何物)


    真没见过这种的……

  5. 麦舒
    *.*.*.*
    链接

    麦舒 2009-08-18 20:12:00

    嗯,我在研究微软的 Linq to SQL,我发现这样的代码。的确,改为接口更好。

  6. shawnliu
    *.*.*.*
    链接

    shawnliu 2009-08-18 20:29:00

    老赵文章的质量是相当不错的 得像老赵看奇 新东西玩的还是太少

  7. Kevin Dai
    *.*.*.*
    链接

    Kevin Dai 2009-08-18 21:10:00

    Three posts one day.highly productive.

  8. andy.wu
    *.*.*.*
    链接

    andy.wu 2009-08-18 21:11:00

    记得很清楚的是,在msdn中,有一篇文章明确说明使用抽象类要好于接口。当时年少的我多年奉为至理,哎...

  9. CoderZh
    *.*.*.*
    链接

    CoderZh 2009-08-18 21:34:00

    Jeffrey Zhao:
    http://ayende.com/这种猛人,平均一个月2、30篇博客的,这才叫博客嘛。


    果然很强!

  10. 路过者[未注册用户]
    *.*.*.*
    链接

    路过者[未注册用户] 2009-08-18 21:34:00

    好像是先有Route类,然后再有RouteBase类的

    通用类库的设计总是比较难,感觉微软mvc开发团队大概会进一步扩展这个类,才用抽象类,而不是接口的

    这个问题可以问Phil Haack,他应该知道怎么回事

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

    温景良(Jason) 2009-08-18 21:37:00

    呵呵,看老赵写得博客精辟

  12. 收到[未注册用户]
    *.*.*.*
    链接

    收到[未注册用户] 2009-08-18 21:47:00

    右边我的情况-最新评论 2个地方老是在动 很晃眼 影响左边的阅读

  13. 阿龍
    *.*.*.*
    链接

    阿龍 2009-08-18 22:53:00

    竟然这样喜欢多继承,那就建议微软恢复抽象类的多继承就可以解决你的问题了.

  14. 老赵
    admin
    链接

    老赵 2009-08-18 22:56:00

    andy.wu:记得很清楚的是,在msdn中,有一篇文章明确说明使用抽象类要好于接口。当时年少的我多年奉为至理,哎...


    抽象类不是没有好处,抽象类的好处就是“升级”不会造成问题。

  15. 老赵
    admin
    链接

    老赵 2009-08-18 22:56:00

    @阿龍
    不是喜欢多继承,而是没有必要的约束是错误的行为。
    多继承也有多继承的问题,所以现在都是“多接口实现”。

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

    HBZ[未注册用户] 2009-08-18 22:59:00

    没准.net 8.0的时候,baidu收购MS
    这个RouteBase会多出一个方法 GetKuangJiSuanXXXX()
    什么的
    (*^__^*)

  17. 老赵
    admin
    链接

    老赵 2009-08-18 23:01:00

    @HBZ
    新浪“浪”计算,腾讯“疼”计算。

  18. 老赵
    admin
    链接

    老赵 2009-08-18 23:02:00

    @HBZ
    不过真的百度收购MS的话,估计可以推广中文编程了。

  19. Gray Zhang
    *.*.*.*
    链接

    Gray Zhang 2009-08-18 23:03:00

    记得MVC中有很多东西不是接口而是个完全抽象类,像HttpRequestBase啊,HttpResponseBase啊……

  20. 老赵
    admin
    链接

    老赵 2009-08-18 23:08:00

    @Gray Zhang
    没错,不过我不太喜欢这个Base命名,其实一开始都是IHttpContext之类的,我比较喜欢。
    不过,如今这样HttpContextBase等接口还是有意义的,我整理一下可能还可以说说。

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

    斯克迪亚 2009-08-18 23:27:00

    我笨了,以前总爱用抽象类,就是因为好继承一些实现好的功能,接口却需要各自分别实现,记得有些文章也是如此说接口不方便的,看了这篇文章才明白原来接口+基类也能实现,看来读代码多了还是大有好处>_<,不读别人代码就陷在自己的思维定式里去了。

  22. 老赵
    admin
    链接

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

    @斯克迪亚
    嗯嗯,接口配合抽象类的做法很常见,再如一例:
    IController -> ControllerBase -> Controller -> XxxController
    其实这也是一种分层,不同的层次使用不同的抽象级别。
    // 好像又是一个可以谈论的话题。

  23. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2009-08-19 00:19:00

    感觉MVC是一个hack into ASP.NET的东西,例如搞一堆Wrapper和Base的做法,这显然不可能堂而皇之地写到Framework Design Guideline 3rd Edition里面去吧,但不写的话代码阅读起来也不容易,总是要去猜测这不是个hack。

  24. 老赵
    admin
    链接

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

    @Cat Chen
    我觉得不能称之为hack吧,这也是提高可测试性的唯一方法——除了使用TypeMock这种基于CLR Profiler,直接hack到runtime中的做法之外。
    就好比要测试一个静态方法,总归就是写一个抽象(接口或抽象类),再写一个Wrapper咯。
    所以我觉得ASP.NET MVC到的确是ASP.NET平台上又一个Layer,和WebForms平等的。
    所以ASP.NET MVC没有用常见的Hack手段,比如反射什么的……旧版的ASP.NET AJAX还用了反射。
    // 当然新版发布之后就没有这种Hack了,毕竟是微软自己的东西,修改一些内部实现方式都是一句话就实现了。

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

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

    假如支持RouteBase的作者的话,我觉得可能是为了预留,接口或许该在有很强的契约语义下用比较好,并不一定什么都得用接口,如果一个抽象类主要是为了提供一些复用,相对没有接口那么强的契约语义,非要给它套个接口代码要多写老多,而且,如果接口发生变动,后果比较严重,而抽象类就相对没那么严重,对被封装的部分可以随意更改,而接口不行。不过话说回来,如果这个类没有任何实现,为什么不先把它删掉呢?等需要时可以再补回来啊,要不然这一public不是影响深远了?

  26. 老赵
    admin
    链接

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

    @something
    你说的没错,我应该说的更完整一些,正打算明天谈更多这方面的东西。
    RouteBase是有用到的,其实就是作为Route的抽象。
    代码都是基于这个抽象写的,程序员可以自行扩展。

  27. flyingchen
    *.*.*.*
    链接

    flyingchen 2009-08-19 00:36:00

    我就喜欢搞这样的设计
    Interface<----Base<----Implement
    如果有时仅仅为了复用一些工具性的东西,而非必须的可复用逻辑,还是不要这样,比较耦合度高了些。个人感觉这个实现不太好

  28. 老赵
    admin
    链接

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

    @flyingchen
    嗯,继承的耦合度是比较高的,如果helper方法或组合就可以实现的话,就不用继承。

  29. flyingchen
    *.*.*.*
    链接

    flyingchen 2009-08-19 00:42:00

    Framework Design Guildlines这本书电子版有否,呵呵,mail份我啊

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

    Gray Zhang 2009-08-19 00:43:00

    另外一种比较常见的设计是
    多个interface -> 多个helper --> 利用聚合helper由concrete class实现多个interface
    这个设计依稀在Unity的代码中见过不少,感觉还是很清晰并且具有良好的扩展和延伸的

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

    斯克迪亚 2009-08-19 01:41:00

    Jeffrey Zhao:
    @斯克迪亚
    嗯嗯,接口配合抽象类的做法很常见,再如一例:
    IController -> ControllerBase -> Controller -> XxxController
    其实这也是一种分层,不同的层次使用不同的抽象级别。
    // 好像又是一个可以谈论的话题。


    的确,谢谢指点:)

  32. 双鱼座
    *.*.*.*
    链接

    双鱼座 2009-08-19 07:08:00

    你那篇关于线程安全的说法我非常认同,但是你这个意见我不能完全接受。设计原则归设计原则,并不是不能违反的。抽象类对比接口还是我很多好处的。例如扩展方法、操作符重载。此处没有操作符重载,或者与扩展方法有关。

  33. BreezeWoo
    *.*.*.*
    链接

    BreezeWoo 2009-08-19 08:35:00

    Interface<----Base<----Implement
    Interface很强大。

  34. Herist
    *.*.*.*
    链接

    Herist 2009-08-19 08:40:00

    使用抽象类的原因
    是不是可以用C#3.0 的扩展方法

  35. 老赵
    admin
    链接

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

    双鱼座:你那篇关于线程安全的说法我非常认同,但是你这个意见我不能完全接受。设计原则归设计原则,并不是不能违反的。抽象类对比接口还是我很多好处的。例如扩展方法、操作符重载。此处没有操作符重载,或者与扩展方法有关。


    设计原则就是倾向性,当然可以违反,现在违反了不照样可以编程,但是不好。
    接口就不能使用扩展方法了吗?以下不可以吗??
    static void Do(this ISome some)

  36. 老赵
    admin
    链接

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

    Herist:
    使用抽象类的原因
    是不是可以用C#3.0 的扩展方法


    我不知道哪里有资料说过接口不能有扩展方法,实在是很奇怪。
    这里有个实例,是基于ICustomAttributeProvider的扩展方法。
    http://www.cnblogs.com/JeffreyZhao/archive/2009/01/07/AttachDataExtensions.html

  37. 老赵
    admin
    链接

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

    flyingchen:Framework Design Guildlines这本书电子版有否,呵呵,mail份我啊


    没有啊,这本找不到。

  38. 李永京
    *.*.*.*
    链接

    李永京 2009-08-19 09:08:00

    老赵学习NHibernate作者了,有什么都发博客,NHibernate作者还有自己的小金库呢~~

  39. 老赵
    admin
    链接

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

    @李永京
    什么叫小金库?

  40. 李永京
    *.*.*.*
    链接

    李永京 2009-08-19 09:35:00

    @Jeffrey Zhao
    rhino-tools 各种尝试性和成熟的代码项目吧,Repository库

  41. 要有好的心情
    *.*.*.*
    链接

    要有好的心情 2009-08-19 09:36:00

    我也觉得.net 框架中的Membership相关基类以及MembershipUser应该搞成接口。静态类Membership中应该引用IMembershipProvider接口和IMembershipUser接口。

  42. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-08-19 09:38:00

    呵呵,我刚开始学习ASP.NET MVC时也有这个疑惑,其它的接口——抽象类——实现类的流水线都很清晰,只有这个RouteBase显得很不合群,不过水平有限不敢质疑……
    // 长文章不但写得累,看着也累,在微博客盛行的今天,一定要支持短小但有味道的技术文章

  43. 每一天[未注册用户]
    *.*.*.*
    链接

    每一天[未注册用户] 2009-08-19 09:48:00

    感觉老赵的东西都是为了卖弄而写的。浏览他的博客,对实际开发的确没有太大的帮助。

  44. kenny.guo
    *.*.*.*
    链接

    kenny.guo 2009-08-19 09:55:00

    传说中服务器推送?

  45. 老赵
    admin
    链接

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

    李永京:
    @Jeffrey Zhao
    rhino-tools 各种尝试性和成熟的代码项目吧,Repository库


    哦,我起步晚,就直接用他或别人的东西了,嘿嘿。

  46. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-08-19 10:03:00

    Jeffrey Zhao:

    flyingchen:Framework Design Guildlines这本书电子版有否,呵呵,mail份我啊


    没有啊,这本找不到。


    我这里有,45M

  47. 老赵
    admin
    链接

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

    每一天:感觉老赵的东西都是为了卖弄而写的。浏览他的博客,对实际开发的确没有太大的帮助。


    这叫心得体会,我不想写所谓“帮助大”的东西,帮助最大的莫过于“XX编程一百例”,我肯定不会写。
    当然,我也不承认我这个对实际开发帮助小,因为我本来就是搞实际一线开发的,如果这些帮助小,那么又是什么捏?

  48. 老赵
    admin
    链接

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

    kenny.guo:传说中服务器推送?


    什么?

  49. James.Ying
    *.*.*.*
    链接

    James.Ying 2009-08-19 10:06:00

    越来越接近大众了 顶

  50. 好好学习,天天向上
    *.*.*.*
    链接

    好好学习,天天向上 2009-08-19 10:16:00

    老赵,我偶像

  51. kenny.guo
    *.*.*.*
    链接

    kenny.guo 2009-08-19 10:23:00

    这英文,难了

  52. 232423423423[未注册用户]
    *.*.*.*
    链接

    232423423423[未注册用户] 2009-08-19 12:39:00

    你最好多了解下抽象类和接口的区别

  53. 老赵
    admin
    链接

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

    232423423423:你最好多了解下抽象类和接口的区别


    我认为我了解得挺多了,不知道还欠缺哪部分?

  54. sa1[未注册用户]
    *.*.*.*
    链接

    sa1[未注册用户] 2009-08-19 17:27:00

    Jeffrey Zhao:
    @横刀天笑
    话说,我打算以后写的东西小一点,多写一点。
    以前把这里当“博客园”,为首页而发,篇篇很长,三五千字,很累。
    以后我打算把这里当自己的博客,想到一些细节或技术方面的体会就写一篇——最多不放到首页上去,
    所以欢迎大家通过RSS订阅(http://www.cnblogs.com/JeffreyZhao/rss)。
    http://ayende.com/这种猛人,平均一个月2、30篇博客的,这才叫博客嘛。



    你TMD又在装FRJJ的B了

  55. 老赵
    admin
    链接

    老赵 2009-08-19 17:30:00

    @sa1
    我错了,人ayende发博客,每月30篇算极少的,60篇普通水平,90篇比比皆是,06年3月发了143篇,平均每天近5篇。
    咱再疯狂也不能和他相比啊。

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

    中华小鹰 2009-08-20 13:13:00

    @flyingchen

    BreezeWoo:
    Interface<----Base<----Implement
    Interface很强大。



    对该模式没有深的研究。实际上,我很纳闷,这样有啥好处呢?坏处却很明显,复杂度提高了。

  57. pythonic
    *.*.*.*
    链接

    pythonic 2009-08-22 10:16:00

    @Jeffrey Zhao
    《Programming Microsoft ASP.NET 3.5》讲述ASP.NET的Provider模型时,穿插了一段有关BaseClass和interface的讨论,可以帮助理解BaseClass和interface的使用场合:
    An interface is a collection of logically related methods that contains only member definitions and no code. An interface type is a partial description of a type, which multiple classes can potentially support. In other words, a good interface is one that is implemented by a number of different types and encapsulates a useful, generalized piece of functionality that clients want to use. That's why many interfaces just end with the suffix "able", such as IDisposable, IComparable, and IFormattable. If an interface has only one useful implementing class, it is likely the offspring of a bad design choice. As a practical rule, new interfaces should be introduced sparingly and with due forethought.

    A base class defines a common behavior and a common programming interface for a tree of child classes. Classes are more flexible than interfaces and support versioning.

    If you add a new method to version 2.0 of a class, any existing derived classes continue to function unchanged, as long as the new method is not abstract. This is untrue for interfaces.

    In light of these considerations, the emerging rule is that one should use base classes instead of interfaces whenever possible (which doesn't read as, "always use base classes"). To me, base classes appear to be an excellent choice, as far as the provider model is concerned.

    In more general terms, the debate between base classes and interfaces has no easy answer. In some application-specific cases, in fact, one could argue that using a base class for a behavior that can be described with an interface takes away the option of deriving that new class from a custom
    base class that could lead to an overall better design. ASP.NET uses base classes for its provider model, and that is the pattern you must follow.

  58. chenleinet
    *.*.*.*
    链接

    chenleinet 2009-08-22 21:57:00

    接口不是没有好处,接口的好处就是“升级”不会造成问题,为什么接口不会造成升级问题呢,抽象类,为什么会呢

  59. 老赵
    admin
    链接

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

    chenleinet:接口不是没有好处,接口的好处就是“升级”不会造成问题,为什么接口不会造成升级问题呢,抽象类,为什么会呢


    我说反了,应该是抽象类升级不会有问题。具体可以看最后引用的文章。

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我