最近参加了一些开发技术上的面试,不少应聘者都声称“精通基于MVC设计模式的三层架构”。 这种提法意味着,MVC是三层架构的一种实现方式。
面试中还有人提到,使用了“MVC中的EF”。忍不住追问,EF是MVC的组成部分?得到了肯定的回答。
参加面试的也有人将MVC和三层架构对立起来,声称他们的项目使用了MVC,而没有使用多层架构。或者反之。
因此,有必要探究一下:MVC和三层架构之间是什么关系。
很多人会顺嘴说到“MVC设计模式”,让我们先偏离一下正题,先对这个问题较较真:MVC是不是一种设计模式。
在经典之作《设计模式——可复用面向对象软件的基础》(后文简称GoF)中,讨论了SmallTalk中的MVC:
MVC由三类对象组成:模型、视图和控制器。模型是应用中的对象,视图是模型的屏幕展现,控制器定义了用户界面如何响应用户的输入。在模型和视图间建立订阅/通知机制,MVC实现了对二者的解耦。无论模型的数据什么时候发生变化,都要通知建立在其上的视图,这样视图才能进行更新。我们可以在一个模型上关联多个视图,每个视图可以有不同的表现形式。也可以为模型新建视图,而不必修改模型本身。
图1. MVC的原始定义(该图来自Model View Controller: History, theory and usage,推荐)
SmallTalk中的MVC,其意图在于构建整个应用程序,被称为一种Paradigm。GoF并未将MVC列入23个设计模式中,而是分析了MVC用到了哪些设计模式:策略模式、模板方法模式、装饰器模式等。
鉴于此,不妨将MVC理解为一种应用的架构。其粒度要远远超过设计模式。一个设计模式一般包含2-5个类,MVC则远不止此。实际上,MVC也是讨论应用架构的书中经常讨论的内容,如Martin Flower的《企业应用架构模式》等。
SmallTalk MVC出现于上世纪80年代,对我们现在的很多小伙伴来说,那属于史前时代,因为那时候Web应用还没有踪影。
MVC大行其道,很大程度上应该归功于Web应用的兴起,特别是JSP Model2的实现。
最初,Web应用还较为简单,JSP可以包打天下:将界面展现、逻辑处理集于一身。JSP中既包含页面展现的内容,以HTML为主,也包含逻辑处理的内容,以Java代码为主。这种处理模式称为JSP Model 1。
图 2 JSP Model 1
后来,随着应用复杂程度的增加,JSP Model 1带来问题越来越多。人们就把JSP的职责独立出来,还原到其原来的定位:页面展现,而将请求处理、数据访问等功能从其中剥离出来。这形成了处理模式Model 2。
图 3 JSP Model 2
Model 2将系统中的展现、数据访问和请求处理拆分开来,自然令人想到MVC,因此,Model 2和MVC基本上画上了等号。但Web年代的MVC已经不是最初的MVC,其中最明显的区别是,取消了View对Model的监听,因为在B/S环境下,实现这个得不偿失。
Model 2最为著名的实现是Struts,这是在J2EE世界;在.NET世界中,自然ASP .NET MVC。是的,你没看错,.NET MVC是JSP Model 2在.NET中的实现。
因此,我们考虑MVC就以Structs和.NET MVC为主。
MVC从最初一直到Struts,其意图都在于构建整个应用程序,而三层架构也是用于构建完整的应用。如果将其放在一起,会有什么对应关系呢?
图 4 MVC与三层架构的对应关系是怎样的?
我们分别针对Struts和.NET MVC,探究一下这个问号。
Struts相当忠实地实现了Model 2。其部件与三层的对应关系,分析如下:
这种对应关系可以图示如下:
图 5 Struts与三层架构的对应关系
考虑一下在三层架构中,SSH各自的势力范围,这种对应关系可能更为清楚一些:
从概念上看,JSP Model 2并没有清晰地界定三层架构,特别是对于业务逻辑层刻画很少。由于Struts是对JSP Model 2的忠实实现,这个问题在Struts中同样存在。
事情到了ASP .NET MVC有了些变化。这个变化突出表现在对模型的定义。在.NET MVC中,可以使用标签定义模型属性的展现和验证方式。
例如:
namespace Models
{
public class Student
{
[DisplayName("联系人")]
[Required(ErrorMessage = "对不起,联系人姓名不能为空")]
public string ContactName { get; set; }
[DisplayName("移动电话")]
[CustomValidation(typeof(StudentModel), "CheckPhoneNumbers")]
public string MobilePhoneNumber { get; set; }
……
}
}
[DisplayName]标签用于创建该属性的标签,[Required]、[CustomValidation]等标签则用于控制对用户输入结果的验证。这些标签属于展现层的功能,加上了这些标签的模型,也就被定位于展现层,而不再适用于数据访问。因此,.NET MVC中的模型,更为准确的名称应当是视图层的模型,即ViewModel,简称VM。
这样,整个.NET MVC的定位也明确了:它属于展现层。
图 6 ASP .NET MVC与三层架构的对应关系
在产品/工具选型上,则可以借鉴SSH,确定各项在.NET中的替代品。在我们这里的讨论中,Struts的替代品自然是.NET MVC,Spring则可以选择Spring .NET或Enterprise Library,Hibernate的替代品,可以考虑NHibernate或Entity Framework。
本文的关注点在于MVC与三层架构之间的关系,对于三层架构自身则较少提及,更没有展开,例如,有的实现中扩展为更多层,我们笼统地称为三层架构。
关于MVC与三层架构之间的关系,可以归纳如下: