尽可能地使用强类型数据
2009-02-27 08:19 by Jeffrey Zhao, 14429 阅读, 68 评论, 收藏, 编辑
我们继续来谈《最佳实践》,这次的主题便是“强类型”。
一直说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中生成导航栏链接的代码是这样的: