控制流和数据流

数据流

数据流——描述程序运行过程中数据的流转方式及其行为状态

在MVC模型中,Model层的本质就是“数据”,数据在MVC的各个构成要素中流转并且在不同的层次扮演着不同的角色。当程序运行起来之后,我们会发现正是由于数据的流转,才使得原本孤立和静态的元素形成了互动。因此,我们可以得出结论——真正贯穿MVC框架并且将MVC的各个模块黏合在一起的是数据。数据作为黏合剂,构成了模块与模块间的互动载体,把MVC真正融合在了一起。

因此我们可以看到在MVC模型中,Model层实际上是一个动态元素,它作为数据载体流转于程序之间,并在不同的程序模块中表现出不同的行为状态,这就是形成数据流的本质

数据载体的选择

在Model层,我们对于数据的关注点主要有两个:数据存储和数据传输。 因而Model层总是以“属性对象模式”作为观察角度进行建模的。在这种情况下,数据(Model)所扮演的是一个载体的角色。

所谓载体,它首先必须具备一定的数据结构,那么到底什么样的数据结构和数据形式最适合用作Web层的传输介质呢?不同的Web层框架基于它们自己的理解,给出了众多不同的答案。在这里,我们选择其中比较典型的三种进行分析。

使用Map作为数据载体

Map是一个基于键值对(Key-Value)的数据结构。使用Map作为数据载体,是一种非常直观的思维结果。因而,Servlet标准在设计HttpServletRequest规范时就使用了类似Map的结构来进行数据交互。 如果在应用程序中使用Map作为数据交互的载体,实际上是沿袭了Servlet标准中规定的设计方式。这种方式虽然简单而有效,却存在着一些致命的问题:

  • Map作为一个原始的容器数据结构,弱化了Java作为一个强类型语言的功能—— Map的本质是一个容器结构,我们在操作容器结构时,更加关注其“容器”的性质而并非存储于容器内的元素。而从我们的需求来看,我们更加关心的是隐藏在容器中的元素。 所以,当这些很重要的数据元素失去了基本强类型语言的功能支持时,程序的可读性和可维护性显然都要大打折扣。

  • 使用Map中的键值(Key)作为数据存取的依据,使得程序的可读性大大降低—— 当我们需要对Map结构进行存取时,我们的唯一依据就是键值(Key)。从Map本身的数据结构特性来看,它是一个不稳定的数据结构。当这些添加、修改和删除的逻辑散落在程序的各个角落时,我们将很难在某一特定时刻确定当前Map中到底有哪些Key值可以进行存取。

  • 使用Map结构进行数据交互,无法实现必要的类型转化—— 这一点从ServletRequest的接口设计中我们就可以发现。虽然可以根据name取到页面上提交上来的值,但那些值却全部都是String或者String[] 类型。这与上面提到的第一点是相通的,Map无法提供Java所具备的强类型语言的基本功能。这些类型转化方面的功能,我们需要在应用程序中自行处理。 不过Map结构却是最具备灵活度的一种数据结构,因为Map结构对于数据的格式、大小、数量都没有特殊的规定,从而成为最具扩展性的数据载体。在对数据格式要求不高、数据经常发生变化的情况下,还是有许多程序员乐意使用它作为数据交互的载体。

使用FormBean作为数据载体

针对Map结构的种种问题,以Struts1.X 为代表的许多Web框架提出了使用FormBean作为数据交互载体的方案。FormBean是对Map的一个改进,他的出现在一定程度上解决了Map这个容器结构带来的种种问题,因为FormBean在表现形式上是一个强类型的数据结构。就这一点而言,FormBean是一种进步,至少在语法层面,我们获得了强类型语法检查的好处。
以Struts1.X 为例,一个典型的FormBean看上去就像下面这样:

public class UserForm extends ActionForm{
    private String userName;
    private String password;

    // 进行数据校验的方法,主要用于数据格式层面的校验,例如非空、类型判断等
    // 如果要进行业务验证,则应该在Action中进行
    public ActionErrors validate(ActionMapping mapping, HttpServletRequest request){
        return null;
    }

    //省略了setter方法与getter方法
}

除此之外,我们还需要在struts-config.xml中声明一下这个FormBean,如下代码:

<form-beans>
    <form-bean name="UserForm" type="example.UserForm"/>
</form-beans>

站在Struts1.X 的角度,FormBean被设计成MVC模式的M部分,它承担着数据载体的重任。Struts1.X在它的控制层中,把FormBean作为它的一个参数传入到执行逻辑的方法中去作为控制层获取Model层内容的交互途径,如以下代码:

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){

    // 从参数中获得ActionForm,并强转为实际使用的UserForm
    UserForm userForm = (UserForm)form;
    // 从Form中取得数据
    String userName = userForm.getUserName();
    String userPwd = userForm.getUserPwd();

    return mapping.findForward(forward);
}

作为MVC设计的重要部分,FormBean被框架赋予太多的功能,而这些功能又需要被框架本身使用到。这就使得FormBean与整个框架形成了一个强耦合。除此之外,越来越多的针对FormBean的质疑被提出:

  • FormBean被强制继承ActionForm—— 从FormBean的存在形式上看,我们可以看到它并不是一个纯真的POJO,而是被强制依赖于Struts1.X 的框架。这意味着我们几乎无法脱离框架和Web容器对FormBean中的逻辑进行测试。同时,FormBean与框架的强耦合使得FormBean难以被其他诸如业务逻辑层或者持久层所使用。为此,曾经一度非常流行一个叫做DTO(Data Transfer Object)的设计模式在中间做协调,这无疑从一个侧面显示了FormBean的尴尬位置。因为它只能在一个逻辑层次被某一类特定的框架相关的代码所使用。

  • FormBean被强制与框架的功能耦合在一起—— 这是许多使用Struts1.X 的程序员最无法接受的一点。例如,FormBean从ActionForm中继承了数据校验机制,甚至FormBean与Struts1.X的标签也耦合在一起。这就使得程序失去了许多灵活性和扩展性。

  • FormBean在参数传递非常复杂的情况下,几乎无法工作—— FormBean看上像一个普通的POJO,却不具备POJO的基本功效。当页面所表达的逻辑非常复杂时,一个页面上的所有元素实际上需要被归并到多个Java实体中才足以满足数据传递的基本要求。然而,FormBean却严格地对应一个页面中所有的元素映射,导致我们不得不在FormBean中编写大量互相之间毫无逻辑关联性的字段,这对于许多程序员而言几乎是一场噩梦。
    面对种种质疑,Struts1.X在后续的版本中做了种种改进。但是,FormBean作为一个整体成为MVC架构中的一部分这样一个概念却从来没有被放弃,因此它也成为众多Struts1.X 诟病的原因之一。

使用POJO作为数据载体

FormBean已经在数据载体的形式上做出了榜样。从Java语言自身的角度来讲,使用POJO进行数据交互也是我们的最终目标,因为它至少可以为我们带来一些显而易见的好处:

  • 作为JavaBean,POJO是一个具备语义的强类型,不仅能够享受编译器的类型检查,还能够自由定义我们所需要表达的语义。

  • POJO不依赖于任何框架,可以在程序的任何一个层次(如业务逻辑层,甚至是持久层)被复用。

  • POJO突破了FormBean对于页面元素唯一对应的限制,我们可以将一个页面中的元素自由映射到多个POJO中去。

控制流

控制流——控制程序逻辑执行的先后顺序,控制流实际上是数据流融入控制层之后形成的逻辑处理和程序跳转的结果

控制层的四大职责:

  • 控制层负责请求数据的接收
  • 控制层负责业务逻辑的处理
  • 控制层负责响应数据的收集
  • 控制层负责响应流程的控制

控制流之所以能够称为控制流,完全是因为它所控制的对象是数据,数据在逻辑处理过程中的形式和状态的变化,一定程度上促成了控制层的逻辑处理和程序跳转的结果。

控制流的细节

控制层的核心职责是处理业务逻辑,这一结论直接为开发框架指明了目标: 控制层应该更加关注其核心的职责,而其他的辅助逻辑则由框架帮忙来实现。 为了完成这一目标,以XWork为首的开发框架就开始对位于控制层的这四段代码进行规划。 因为我们发现,只有对业务逻辑的处理是我们在控制层所关心的核心内容。 而除此之外的代码,则应该通过合理的设计,转化为一个标准而规范的事件处理流程。

要完成这种对事件处理流程的规范化,需要若干个循序渐进的步骤来完成:

  • 划分事件处理流程步骤

  • 定义事件处理节点对象

  • 组织事件处理节点对象的执行次序


参考:整理归纳自《struts2技术内幕——深入解析struts2架构设计与实现原理》

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试