盡可能地使用強類型數據
2009-02-27 08:19 Jeffrey Zhao 閱讀(21218) 評論(69) 收藏 舉報我們繼續來談《最佳實踐》,這次的主題便是“強類型”。
一直說C#是強類型語言,通俗地講,便是指C#中的“變量”在開發時的類型便是明確的:String便是String,Int32就是Int32,毫無爭議。強類型的好處有很多,張嘴便可隨意舉上幾例:
- 能夠享受代碼提示功能
- 能夠獲得重構工具的支持
- 能夠在編譯期發現更多錯誤
- ……
不過C#也不是“絕對”的強類型語言,因為它也有弱類型,那就是Object。我們知道Object是所有類型的最終基類,任何類型的對象都可以使用Object來引用。可是一旦轉化成Object的變量之后,代碼提示便消失了;即使我們“明確”對象的確切類型,也必須通過Cast才能使用——更何況它形成了一種被“濫用”或“誤用”的機會。例如一段錯誤代碼可能會傳入一個不符合約定類型的對象,那么就會造成錯誤。更嚴重的是,這樣的錯誤可能只要在“運行時”才能被發現,編譯器對此無能為力。
類似的“實踐”其實很多,例如“方法盡可能接受抽象的類型,而返回具體的類型”。
ASP.NET MVC中對于“強類型”一說最典型的方面便是視圖。在ASP.NET MVC中每種視圖(View,Partial,Layout)都可以選擇弱類型和強類型兩種“基類”,兩者的區別便是Model的類型。強類型視圖的唯一區別便是其Model屬性為范型參數所指定的類型——而弱類型則自然就是Object了。在這里我提出一個“最佳實踐”:總是使用強類型的視圖,并且所有數據都從Model中獲取。這么做可能會在一定程度上增加了代碼量,因為我們需要為每個視圖建立一個Model,不過我認為這是值得的。在強類型的視圖中,VS中能夠對Model的各種成員做出豐富的代碼提示,我們便可以快速地輸入代碼,并確保不會出現“拼寫”之類的低級錯誤。如果使用了ViewData這個弱類型的字典,那么每次我們獲取某個值之后,都必須將其Cast成具體的類型才能使用,這無疑使視圖模板變得復雜而難以維護。
其實以上的準則還有另一個層面的內容。使用項目模板安裝了ASP.NET MVC之后,在~/Views/Shared/LogOnUserControl.ascx文件中可以發現對Page.User屬性的直接訪問。這自然可以運行,但是其帶來的后果是“弱化”了aspx/ascx/master文件的“模板”概念,而重新讓其意識到“我是一張頁面”。在ASP.NET MVC框架中,我們要時刻記著“項目中沒有頁面”,我們始終使用Controller中的Action方法來處理請求,而每個請求需要對應一個磁盤上“物理文件”的思維方式一定需要改變(無論是在用WebForms模型還是MVC模型)。直接訪問Page.User屬性也使我們很難為視圖進行獨立的單元測試,因為視圖與HttpContext直接耦合,而HttpContext的Mock相當困難。
在《最佳實踐》的示例中,我為每個視圖都構建了一個Model,它們都在MyMvcDemo.Web.UI.Models項目中。值得一提的是,由于業務的需要,視圖之間很可能出現“數據共享”。例如View和Layout(更ASP.NET的說法是Page與Master Page)之間共享同一個Model。更進一步,兩者之間其實是多對多的關系,如果我們為每個ViewPage定義了強類型的Model,又如何應對同一個ViewPage套用不同ViewMasterPage的情況呢?這個問題最自然的解決方式便是使用接口。一個Model對象不可以有多個父類,但是完全可以實現多個接口。因此,我們往往為強類型的ViewMasterPage指定一個接口作為其泛型參數。從示例中您也可以發現,Site.Master的類型為ViewMasterPage<ISiteMasterModel>,而每個ViewPage的Model類型都實現了ISiteMasterModel接口。
使用ViewData的另一個壞處是必須使用字符串作為鍵進行訪問。字符串是什么?是常量。分散在各處的常量是維護性的大敵,而使用ViewData則幾乎無可避免地將字符串常量分散在控制器和視圖兩個地方——您可能會覺得,使用枚舉不就解決這個問題了嗎?如果這么做,相當于為每個視圖定義不同的枚舉類型,那么我們為什么不直接構造強類型的Model呢?使用字符串作為鍵的另一個壞處是無法在編譯期發現問題:如果您一不小心拼寫錯誤了怎么辦呢?您可能會覺得,原本aspx就必須到運行時才會動態編譯,不過現在ASP.NET MVC的模板已經增加了相關的MSBuild Task,或者使用ASP.NET的預編譯功能,都可以在編譯時對視圖進行檢查了。這也是我建議大家使用aspx,而不是另一種DSL來構建視圖的原因——aspx的周邊支持實在是太豐富了。
不過在視圖中,字符串常量并非只出現在ViewData的訪問上。例如,使用ASP.NET MVC框架模板創建項目之后,Site.Master中生成導航欄鏈接的代碼是這樣的:
<ul id="menu"> <li><%= Html.ActionLink("Home", "Index", "Home")%></li> <li><%= Html.ActionLink("About", "About", "Home")%></li> </ul>
代碼使用了Html.ActionLink這個輔助方法生成一個導向至某個Action的鏈接,只可惜這里又出現了字符串參數表示的Controller名和Action名。事實上,如果您下載了ASP.NET MVC RC的源代碼之后,能夠發現其中的MvcFutures項目中包含了“強類型”的ActionLink輔助方法,于是我們的代碼就能修改為如下模樣(參見《最佳實踐》的示例):
<li><%= Html.ActionLink<HomeController>(c => c.Index(), "Home") %></li> <li><%= Html.ActionLink<HomeController>(c => c.About(), "About") %></li> <li><%= Html.ActionLink<AccountController>(c => c.Register(), "Register") %></li> <li><%= Html.ActionLink<AccountController>(c => c.List(1), "Admin") %></li>
這下我們便可以使用Lambda Expression這個強類型的表示方法來“告訴”ActionLink方法究竟該生成導向至哪個Action方法的鏈接。甚至我們可以在“調用”Action方法的同時指定其參數——例如最后一行代碼,指定將查看“第一頁”內容,而這些數據在交給URL Routing的配置后便能得到我們想要的URL。MvcFutures中還定義了其他類似的輔助方法,例如UrlHelper中的輔助方法則會直接生成一個URL鏈接——而不是一個<a />元素。可惜目前MvcFutures中的這些輔助方法編寫的并不完全“正確”,因為它直接把方法名作為Action的名稱來使用,而ASP.NET MVC從某個預覽版開始引入了ActionNameAttribute,可以為一個Action方法指定一個不同的Action名稱。因此如果您使用ActionNameAttribute來改變Action名,則很可能您需要構建自己的輔助方法。不過這條準則依舊成立:使用強類型的表示法生成URL地址。
如果您要使用MvcFutures中的內容,則必須自行編譯MvcFutures項目。有一點值得注意,如果您使用直接編譯ASP.NET MVC RC解決方案所得到的Microsoft.Web.Mvc.dll,就會發現它依賴的是“System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null”,而您開發時所使用的,也就是安裝在系統中的程序集是“System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35”,兩者不同自然難以兼容。幸運的是,您只要手動修改一下MvcFutures項目的程序集引用就可以了,對您來說這一定不是問題。
看到這里,不知道您是否發現了一個比較“嚴重”的問題,其嚴重程度大大降低了這些負責生成URL的輔助方法的可用性。這是個什么問題呢?又該如何解決呢?我在這里先賣個關子,下個星期我將公布這個問題,以及它的解決方法——這部分內容并沒有包含在上次《最佳實踐》的講座中,算是一個“保留節目”吧。:)
浙公網安備 33010602011771號