分析ASP.NET Routing最令人疑惑的設(shè)計(jì)
- public abstract class RouteBase
- {
- protected RouteBase() { }
- public abstract RouteData GetRouteData(HttpContextBase httpContext);
- public abstract VirtualPathData GetVirtualPath(
- RequestContext requestContext,
- RouteValueDictionary values);
- }
它為什么是一個(gè)沒(méi)有任何實(shí)現(xiàn)的抽象類(lèi),而不是一個(gè)接口(如下)?
- public interface IRoute
- {
- RouteData GetRouteData(HttpContextBase httpContext);
- VirtualPathData GetVirtualPath(
- RequestContext requestContext,
- RouteValueDictionary values);
- }
這樣做難道不更漂亮一些嗎?這樣代碼中都可以使用IRoute類(lèi)型,避免RouteBase這種令人反感的命名出現(xiàn)(個(gè)人感覺(jué),不知道有沒(méi)有同意的群眾)。退一步說(shuō),命名上的“美感”是小事……但是抽象類(lèi)在.NET平臺(tái)中就產(chǎn)生了一個(gè)非常嚴(yán)重的限制:一個(gè)類(lèi)無(wú)法繼承多個(gè)基類(lèi)。因此,在.NET平臺(tái)上總是更傾向于使用接口,而不是抽象類(lèi)。
但是接口里不可以有任何實(shí)現(xiàn),那么可復(fù)用的功能又放在哪里比較合適呢?《Framework Design Guildlines》告訴我們:在一個(gè)類(lèi)庫(kù)中,***為接口定義一個(gè)默認(rèn)實(shí)現(xiàn),這樣也是開(kāi)發(fā)人員進(jìn)行“擴(kuò)展”的一個(gè)“參考”。也就是說(shuō),如果真有什么需要復(fù)用的實(shí)現(xiàn),我們完全可以這么做:
- public abstract class RouteBase : IRoute
- {
- // reusable implementations
- }
- public class Route : RouteBase
- {
- // concrete implementations
- }
事實(shí)上,.NET平臺(tái)上有許多類(lèi)庫(kù)也遵循了這個(gè)做法。一個(gè)典型的做法便是ASP.NET AJAX框架的Extender模型:
- public interface IExtenderControl { }
- public abstract class ExtenderControl : Control, IExtenderControl { }
甚至在ASP.NET AJAX Control Tookit項(xiàng)目中,還有更進(jìn)一步的擴(kuò)展:
- public abstract class ExtenderControlBase : ExtenderControl { }
- public class AnimationExtenderControlBase : ExtenderControlBase { }
- public class AutoCompleteExtender : AnimationExtenderControlBase { }
看來(lái)微軟在項(xiàng)目團(tuán)隊(duì)內(nèi)部推廣《Framework Design Guidelines》還不夠徹底。
在.NET平臺(tái)下,一個(gè)沒(méi)有任何實(shí)現(xiàn)的,純粹的抽象類(lèi)可謂有百害而無(wú)一利。我很懷疑寫(xiě)這段代碼的人剛從C++切換到C#——但是ASP.NET Routing中其實(shí)也有接口(如IRouteConstraint),為什么作者自己沒(méi)有意識(shí)到,也沒(méi)有人提出不同意見(jiàn)呢?微軟開(kāi)發(fā)團(tuán)隊(duì)?wèi)?yīng)該有著嚴(yán)格的Code Review過(guò)程,怎么會(huì)讓這樣的代碼正式發(fā)布?要知道一個(gè)接口一旦公開(kāi),就不可以刪除了。也就是說(shuō),微軟很難彌補(bǔ)這個(gè)錯(cuò)誤。
如果是方法名不好,或者職責(zé)有些不明確,這樣還可以在舊方法上添加ObsoleteAttribute(這樣編譯器便會(huì)提示用戶(hù)這個(gè)方法已經(jīng)過(guò)期),并且將舊方法的調(diào)用委托給新的實(shí)現(xiàn)。例如:
- 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();
- ...
- }
可是,現(xiàn)在的問(wèn)題是一個(gè)“類(lèi)”,而這個(gè)類(lèi)已經(jīng)無(wú)處不在了,例如在RouteData中有一個(gè)屬性Route,它便是RouteBase類(lèi)型——如果將其修改為IRoute接口,那么至少也需要項(xiàng)目重新編譯之后才能夠“升級(jí)”。而作為一個(gè)公開(kāi)類(lèi)庫(kù),尤其是.NET這種成熟框架來(lái)說(shuō),應(yīng)該做到“無(wú)痛”才對(duì)。
本文來(lái)自趙劼的博客園文章《ASP.NET Routing中最令人摸不著頭腦的設(shè)計(jì)》
【編輯推薦】