白话MVC(一)Model的产生及处理

仪山湖 发布于 2012/09/29 00:33
阅读 2K+
收藏 12

最近在带一“徒弟”,领悟能力很高,对我的能力也提出了新的要求,在“带”的过程中,发现了一有趣的现象,很多东西 会用,但是要想用清楚的语言把这些技术描述出来,还是很有难度的。特别是在讲Spring框架的使用,不少知识点的使用已经和学校课本上所教的东西脱节, “徒弟”理解起某些概念起来感到比较陌生。我也不想告诉他,这东西就是这么用的,按照这样的写法去写代码,就能实现模块功能。在我看来,程序代表的是心中 的想法,对程序员来说,程序是心中想法的最终实现,有必要对一些概念的产生及作用做进一步的探讨。

在 探讨的过程中,发现其实对自己一些概念上的认识也很有提高。好,开头写到这里,遵照无废话的原则,尽力把东西写的言简意赅。希望对初学MVC框架的同学有 帮助,希望对深入了解目前主流的WEB上的MVC框架有一个逻辑概念的铺垫。 如果读者发现这里有写的不正确的地方,请直接告诉我,非常欢迎这样的技术交流。

MVC 的概念其实最早可以追溯到很久很久以前,并不是WEB开发过程中所首创,但是,MVC也适合WEB上的开发,并真正的在WEB开发领域广泛应用。MVC的 第一个字母M是Model,承载着View层和Controller之间的数据传输,是数据传输的载体,通过Model层,解偶了View层和 Controller层。解偶View层和Controller层并不代表View层和Controller层之间没有关系,而是降低复杂度。

为 了便于理解Model层在开发中所起的作用,先拿一个最简单的用户录入来举个例子。假设要从表单中录入用户时需要填写用户名和年龄,表单提交后,服务器端 的Servlet需要拿到表单内填写的内容(这里用ServletRequest来得到数据是为了说明MVC框架中M层所起到的作用),为了得到请求的数 据,Servlet的代码大至这样写

public class TestServlet  extends HttpServlet
      protected void doPost(ServletRequest request, ServletResponse response) throws IOException {
            User user = new User();
            user.setName(request.getParameter("name"));
            user.setAge(Integer.parseInt(request.getParameter("age").toString()));

            //省略业务处理代码 ... 
            
      }
}
 

分析这几行代码,主要干了四件事情

  1. 创建User对象
  2. 得到Request的参数,
  3. 转换age参数类型,
  4. 调用User对象的setter方法,为User对象赋予相关的值

经 过这四个步骤,一个具有业务含义的User对象被创建,作为业务层Bean相关代码的输入条件。从这几行代码里可以看出,Request中的数据转换成具 有业务含义的对象,中间经历了四个“必要而繁琐”的步骤。如果程序员天天写这样的代码,岂不是要疯掉?还好,虽然代码只能一行行的写,但是思想却是可以升 华的。观察一下这四个步骤,其实每个步骤都不复杂,如果写一段代码能自动创建业务对象,并且自动装配这个对象中各属性的值,那岂不是能省很多时间和精力, 让程序员解放出来。

想法可以万能,代码不可万能,在要解放程序员的时间与精力之前,对这个业务类User和表单中的参数作一些约定,以便实现自动的装配框架。约定如下:

  1. User类必须有一个不包含任何参数的默认构造函数
  2. User类中的属性名字和表单中的输入控件的名称一样
  3. User类中的各属性必须有默认的getter、setter方法

那框架代码要实现什么功能呢?以这个例子来说,框架要自动的创建User对象,拿到Request中的参数值,转换类型后,赋给User对象相对应的属性。

有了上面的三条协定后,再有Java一个非常非常重要的功能--反射,的鼎力相助,上面的自动装配的功能可以得以实现一 个比较直观的流程是先扫瞄类中的属性,遍历所有的属性,通过属性名生成setter方法的函数名,通过反射的方法调用这些setter方法,setter 方法的参数则通过从Request中得到,并转换成需要的类型,这里的类型转换也是需要通过反射机制来完成(目前这个流程还只能转换java数据中的基本 类型,对于复合类型的Bean,为了描述这个流程,暂时不展开论述)。

现在假设框架已经能自动的装配User对象,编写服务端代码时,就不再关心如何从Request中取参数了,只需按照前面的三个约定编写User类。但是事情还没有结束,既然自动装配了User对象,那前面示例的Servlet代码是不是可以变成这样?

public class TestServlet  extends HttpServlet
      
      private User user;

      protected void doPost(ServletResponse response) throws IOException {
             
            //省略业务处理代码 ... 
      }
     
      public void setUser(User user) {
            this.user = user;
      }
}

这样的代码看起来却是有些奇怪,为什么doPost方法声明中只有一个ServletResponse参数,而没有ServletRequest参数?因为有框架的自动装配User对象,对于代码的编写者而言,就根本不需要关心ServletRequest这个对象

现 在进行更大胆的一步假设,且约定所有的Servlet的执行会把Request,Response运送(forward)到一个JSP页面,那么,服务端 代码编写时,也不再需要考虑Response对象了,除了前面所说的User类外,再加上一个,这个Servlet的doPost方法执行完后,要把 Request,Response运送到那个页面,假如用show_user.jsp这个页面显示被输入的用户信息。这个时候,回头再审视一下刚刚做出的 假设,会自然的得出下面的结论,可以编写一个与Request、Response无关的类,只关注User类如何编写,返回一个代表要forward的 JSP页面,User对象的装配工作,以及forward本身的这个代码调用都可以交给框架去统一的管理,那真正要编写的服务端代码可以象是这样的

public class TestAction {
      
     private User user;

      public void setUser(User user){
            this.user = user;
      }

      public String execute(ModelVehicle modelVehicle) {
            //业务代码实现
             .... .... ....
            modelVehicle.add("user", user);
            return "show_user.jsp";
      }
}

是不是很像Spring MVC的controller类?为了避免和MVC术语中的Controller这个词冲突,仍然叫它Action吧。这个类已经完全和 Request,Response无关,当它被框架激活执行的时候,它首选需要框架为它准备好User对象,并且调用setUser方法,赋予 TestServlet对象user这个属性,然后execute方法被执行,执行了真正的业务逻辑,同时,把需要在show_user.jsp页面上显 示的数据放到ModelVehicle对象中去,TestAction这个对象执行完execute方法后,需要委托框架把ModelVehicle对象 中的数据forward到show_user.jsp这个页面上。

注意,这里有两个非常重要的约定,就是Action类必须有

public String execute(ModelVehicle modelVehicle)
  这个方法的声明,需要显示到页面上的数据,按照约定,须在execute执行过程中放到ModelVehicle对象中去。为了编写的方便,可以继承一个声明了此方法的抽象类。

基于这样的服务端代码编写方式,需要框架再提供什么功能呢?这里就引出MVC框架中C需要关注的事情:

  1. 保证一个URI请求优先被框架先拦截,框架能根据这个URI知道哪个Action类会被创建,
  2. Action类的execute方法被执行,需要把ModelVehicle中的数据一并forward到Action类execute方法返回的JSP页面。

因为这里还是重点介绍Model层的事,Controller层的事会在后面继续介绍,其实,Controller是MVC框架中一个枢纽,需要花点篇幅去探讨。

从 探讨Model层的过程中,我们发现在Action类中向JSP页面输出数据时,也是采用了Model方式。但是这个Model在处理起来相对简单的 多,JSP 2.0规范中的JSTL标签库很容易的将Request中的属性在页面上显示出来,而且还能做一些简单的运算。因此,MVC框架中Model层的主要关注 点是如何把请求的数据自动装配成Action所需要的bean,除此外,框架Model层还可以提供复合bean自动装配、输入校验、本地化及国际化、字 符集编码转换、多重输出等功能。但是一切的起点是Model层的java bean编写遵照了这三个基本的约定:

  1. 必须有一个不包含任何参数的默认构造函数
  2. 属性名字和表单中的输入控件的名称一样
  3. 各属性必须有默认的getter、setter方法

正是有了这三个约定,让MVC框架Model层实现变得简单,一个好的约定让软件开发变得简单,是不是?

[本文系作者原创,如若转载,请注明出处,新浪微博:@仪山湖]
加载中
0
中山野鬼
中山野鬼

哈。很多玩意,其实都是一会事,无非换了包装和外面彩绳的捆绑方式。或许会有更刺激的SM捆束方法。形式特色嘛。啥流行,就啥方式。。。。

当然拆了后,还是那跟绳子,管你粗细,色彩。。。

不知道有几个看懂我在胡扯什么哈。

提示,你关注啥?捆的花哨,但还是那跟绳子,无非你为了花哨还是为了捆。就怕捆的看似花哨,却不结实。

0
Sephiroth
Sephiroth
看来 @中山野鬼 已经实践过SM,才能打出这么恰当的比方 
中山野鬼
中山野鬼
回复 @仪山湖 : 是啊。。。。曾经常识捆出激情,结果自己被捆了。。。。一顿乱砍,才算出来。哈。
仪山湖
仪山湖
哈哈
0
阿诶么刚阿
阿诶么刚阿
稍有内涵却又通俗易懂的文章,不错
0
仪山湖
仪山湖

引用来自“孙小超”的答案

稍有内涵却又通俗易懂的文章,不错
刚开始嘛,浅显易懂,随着深入的看MVC,会渐渐发现精髓,有什么错误的地方,请直接拍砖
0
Wentasy
Wentasy
纠正个错误,“ 解偶 ”应该是“解耦”。
仪山湖
仪山湖
是的,写错了
0
一千年前的人
一千年前的人

引用来自“中山野鬼”的答案

哈。很多玩意,其实都是一会事,无非换了包装和外面彩绳的捆绑方式。或许会有更刺激的SM捆束方法。形式特色嘛。啥流行,就啥方式。。。。

当然拆了后,还是那跟绳子,管你粗细,色彩。。。

不知道有几个看懂我在胡扯什么哈。

提示,你关注啥?捆的花哨,但还是那跟绳子,无非你为了花哨还是为了捆。就怕捆的看似花哨,却不结实。

你认为不需要花哨的捆绑, 应该直奔主题
对否?
0
中山野鬼
中山野鬼

引用来自“一千年前的人”的答案

引用来自“中山野鬼”的答案

哈。很多玩意,其实都是一会事,无非换了包装和外面彩绳的捆绑方式。或许会有更刺激的SM捆束方法。形式特色嘛。啥流行,就啥方式。。。。

当然拆了后,还是那跟绳子,管你粗细,色彩。。。

不知道有几个看懂我在胡扯什么哈。

提示,你关注啥?捆的花哨,但还是那跟绳子,无非你为了花哨还是为了捆。就怕捆的看似花哨,却不结实。

你认为不需要花哨的捆绑, 应该直奔主题
对否?
复杂点,是有说法的。不过人有思维惯性,有时会为了复杂而复杂。我写C都有时掉进去。所以现在养成个习惯。这样的操作是否有必要性,如果没有必要性,一律不增加设计复杂度。
0
j
jerrytao
mvc这个东西 本身就是一个大框 还得看具体情况具体应用  像java就将就简单Model rails 就讲究fat model 
0
极品渣子
极品渣子
一直都没弄懂mvc,希望这类实在的文章更多一些。
0
郭金凤
郭金凤
对于我来说可谓是顿悟啊
返回顶部
顶部