代码之家  ›  专栏  ›  技术社区  ›  mawia

设计模式基于Web的应用程序[关闭]

  •  352
  • mawia  · 技术社区  · 16 年前

    我正在设计一个简单的基于Web的应用程序。我是这个基于Web的领域的新手,我需要你关于设计模式的建议,比如如何在servlet之间分配职责,创建新servlet的标准等等。

    实际上,我的主页上有几个实体,与它们对应,我们有几个选项,如添加、编辑和删除。早些时候,我为每个选项使用一个servlet,比如servlet1用于添加entity1,servlet2用于编辑entity1等等,这样我们就得到了大量的servlet。

    现在我们正在改变我们的设计。我的问题是,您如何准确地选择如何选择servlet的职责。我们是否应该为每个实体提供一个servlet,它将处理所有的选项并将请求转发给服务层。或者我们应该为整个页面提供一个servlet来处理整个页面请求,然后将其转发到相应的服务层?另外,请求对象是否转发到服务层。

    5 回复  |  直到 8 年前
        1
  •  481
  •   Community Mohan Dere    9 年前

    一个有点体面的Web应用程序由混合的设计模式组成。我只会提到最重要的那些。


    Model View Controller pattern

    您要使用的核心(架构)设计模式是 Model-View-Controller pattern . 这个 控制器 由servlet表示,该servlet(in)直接创建/使用特定的 模型 和 视图 基于请求。这个 模型 将由JavaBean类表示。这通常在 商业模式 包含动作(行为)和 数据模型 其中包含数据(信息)。这个 视图 将由直接访问( 数据 ) 模型 通过el(表达语言)。

    然后,根据操作和事件的处理方式,会有一些变化。最受欢迎的是:

    • 基于请求(动作)的MVC :这是最简单的实现方法。( 业务 ) 模型 直接与 HttpServletRequest 和 HttpServletResponse 物体。您必须自己收集、转换和验证请求参数(大部分)。这个 视图 可以用普通的HTML/CSS/JS表示,它不在请求之间维护状态。这就是为什么 Spring MVC , Struts 和 Stripes 作品。

    • 基于组件的MVC :这很难实现。但是您最终得到了一个更简单的模型和视图,其中所有“原始”servlet api都被完全抽象掉了。您不需要自己收集、转换和验证请求参数。这个 控制器 是否执行此任务并在 模型 . 您需要做的只是定义直接与模型属性一起工作的操作方法。这个 视图 用JSP标记库或XML元素的风格中的“组件”表示,这些元素反过来生成HTML/CSS/JS。的状态 视图 对于随后的请求,将在会话中进行维护。这对于服务器端转换、验证和值更改事件特别有用。这就是为什么 JSF , Wicket 和 Play! 作品。

    另一点需要注意的是,使用国产的MVC框架是一个非常好的学习练习,我建议您只要将其用于个人/私人目的就可以使用。但是一旦你变得专业了,强烈建议你选择一个现有的框架,而不是重新设计你自己的框架。与自己开发和维护一个强大的框架相比,学习一个现有的、开发良好的框架需要的时间要短得多。

    在下面的详细解释中,我将限制自己使用基于请求的MVC,因为这更容易实现。


    Front Controller pattern ( Mediator pattern )

    首先, 控制器 零件应执行 前控制器模式 (这是一种特殊的 中介模式 )它应该只包含一个servlet,它提供所有请求的集中入口点。它应该创建 模型 基于请求提供的信息,例如pathinfo或servletpath、方法和/或特定参数。这个 商业模式 被称为 Action 在下面 HttpServlet 例子。

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            Action action = ActionFactory.getAction(request);
            String view = action.execute(request, response);
    
            if (view.equals(request.getPathInfo().substring(1)) {
                request.getRequestDispatcher("/WEB-INF/" + view + ".jsp").forward(request, response);
            }
            else {
                response.sendRedirect(view); // We'd like to fire redirect in case of a view change as result of the action (PRG pattern).
            }
        }
        catch (Exception e) {
            throw new ServletException("Executing action failed.", e);
        }
    }
    

    执行操作应该返回一些标识符来定位视图。最简单的方法是将其用作JSP的文件名。将这个servlet映射到一个特定的 url-pattern 在里面 web.xml ,例如 /pages/* , *.do 甚至只是 *.html .

    例如,在前缀模式的情况下 /Prime/* 然后你可以调用url http://example.com/pages/register , http://example.com/pages/login 等,并提供 /WEB-INF/register.jsp , /WEB-INF/login.jsp 使用适当的get和post操作。零件 register , login 等可通过 request.getPathInfo() 如上例所示。

    当你使用后缀模式时 *做 , *HTML 等等,然后你可以调用url http://example.com/register.do , http://example.com/login.do 等等,您应该更改此答案中的代码示例(同时 ActionFactory )提取 登记 和 登录 零件由 request.getServletPath() 相反。


    Strategy pattern

    这个 行动 应该遵循 战略模式 . 它需要定义为一个抽象/接口类型,该类型应该基于 通过 抽象方法的参数(这与 Command pattern ,其中抽象/接口类型应根据在 创造 实施)。

    public interface Action {
        public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
    }
    

    你可能想让 Exception 更具体的是,像 ActionException . 这只是一个基本的开球例子,剩下的都由你决定。

    下面是一个例子 LoginAction 它(顾名思义)登录用户。这个 User 它本身反过来就是 数据模型 . 这个 视图 意识到 用户 .

    public class LoginAction implements Action {
    
        public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
            String username = request.getParameter("username");
            String password = request.getParameter("password");
            User user = userDAO.find(username, password);
    
            if (user != null) {
                request.getSession().setAttribute("user", user); // Login user.
                return "home"; // Redirect to home page.
            }
            else {
                request.setAttribute("error", "Unknown username/password. Please retry."); // Store error message in request scope.
                return "login"; // Go back to redisplay login form with error.
            }
        }
    
    }
    

    Factory method pattern

    这个 行动工厂 应该遵循 Factory method pattern . 基本上,它应该提供一个创造性的方法,返回抽象/接口类型的具体实现。在这种情况下,它应该返回 行动 基于请求提供的信息的接口。例如, method 和 pathinfo (pathinfo是请求URL中上下文和servlet路径之后的部分,不包括查询字符串)。

    public static Action getAction(HttpServletRequest request) {
        return actions.get(request.getMethod() + request.getPathInfo());
    }
    

    这个 actions 反过来应该是一些静态的/应用范围的 Map<String, Action> 它包含所有已知的动作。如何填这张地图取决于你。Hardcoding:

    actions.put("POST/register", new RegisterAction());
    actions.put("POST/login", new LoginAction());
    actions.put("GET/logout", new LogoutAction());
    // ...
    

    或者根据类路径中的属性/xml配置文件进行配置:(伪)

    for (Entry entry : configuration) {
        actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
    }
    

    或者动态地基于类路径中实现特定接口和/或注释的类的扫描:(伪)

    for (ClassFile classFile : classpath) {
        if (classFile.isInstanceOf(Action.class)) {
           actions.put(classFile.getAnnotation("mapping"), classFile.newInstance());
        }
    }
    

    记住创建一个“什么都不做” 行动 对于这种情况,没有映射。例如,让它直接返回 request.getPathInfo().substring(1) 然后。


    其他模式

    到目前为止,这些都是重要的模式。

    为了更进一步,您可以使用 Facade pattern 创建一个 Context 类,该类依次包装请求和响应对象,并提供几种方便的方法来委托给请求和响应对象,并将其作为参数传递给 Action#execute() 方法。这会添加一个额外的抽象层来隐藏原始servlet API。你应该以 零 import javax.servlet.* 在每个 行动 实施。在JSF术语中,这就是 FacesContext 和 ExternalContext 课程正在进行。您可以在中找到一个具体的示例 this answer .

    然后是 State pattern 对于这样的情况,您希望添加一个额外的抽象层来分割收集请求参数、转换它们、验证它们、更新模型值和执行操作的任务。在JSF术语中,这就是 LifeCycle 正在做。

    然后是 Composite pattern 对于您希望创建一个基于组件的视图的情况,该视图可以附加到模型中,其行为取决于基于请求的生命周期的状态。在JSF术语中,这就是 UIComponent 代表。

    通过这种方式,您可以逐步向基于组件的框架发展。


    参见:

        2
  •  12
  •   BalusC    16 年前

    在破旧的MVC模式中,servlet是“C”控制器。

    它的主要工作是进行初始请求评估,然后根据初始评估将处理发送给特定的工人。工作人员的职责之一可能是设置一些表示层bean,并将请求转发到JSP页面以呈现HTML。因此,仅出于这个原因,您需要将请求对象传递到服务层。

    不过,我不会开始写生字 Servlet 类。他们所做的工作是非常可预测的和样板式的,框架做得非常好。幸运的是,有许多经过时间考验的候选人(按字母顺序排列): Apache Wicket , Java Server Faces , Spring 举几个例子。

        3
  •  5
  •   Kangkan    16 年前

    imho,如果从责任分配的角度来看,Web应用程序的情况没有太大的区别。但是,保持层中的清晰度。在表示层中保留任何纯粹用于表示目的的内容,例如特定于Web控件的控件和代码。只需将实体保留在业务层中,并将所有功能(如添加、编辑、删除)等保留在业务层中。但是,将它们呈现到浏览器上,以便在表示层中处理。对于.NET,ASP.NET MVC模式在保持层分离方面非常好。研究MVC模式。

        4
  •  3
  •   Afshin Moazami Darxis    11 年前

    我已经用过了 struts 框架和发现它相当容易学习。使用Struts框架时,站点的每个页面都将包含以下项。

    1)每次刷新HTML页面时都会调用所使用的操作。当第一次加载页面时,该操作应填充表单中的数据,并处理Web UI和业务层之间的交互。如果使用JSP页面修改可变Java对象,则Java对象的副本应该存储在表单中而不是原件中,这样原始数据不会被修改,除非用户保存页面。

    2)用于在操作和JSP页面之间传输数据的表单。这个对象应该由一组getter和setter组成,用于JSP文件需要访问的属性。表单还具有一个方法,用于在数据持久化之前对其进行验证。

    3)用于呈现页面最终HTML的JSP页面。JSP页面是HTML和用于访问和操作表单中数据的特殊struts标记的混合体。虽然Struts允许用户将Java代码插入到JSP文件中,但这样做会非常谨慎,因为这会使代码变得更难阅读。JSP文件内部的Java代码很难调试,不能进行单元测试。如果发现自己在JSP文件中编写了超过4-5行Java代码,则代码可能会被移动到操作中。

        5
  •  3
  •   Ravindra babu    8 年前

    巴鲁斯 优秀的答案涵盖了Web应用程序的大多数模式。

    某些应用程序可能需要 Chain-of-responsibility_pattern

    在面向对象设计中, 责任链 模式是由命令对象源和一系列处理对象组成的设计模式。每个处理对象都包含定义它可以处理的命令对象类型的逻辑;其余的对象将传递给链中的下一个处理对象。

    使用案例使用此模式:

    当处理请求(命令)的处理程序未知并且此请求可以发送到多个对象时。一般情况下你会 继承人 反对。如果当前对象无法处理请求或部分处理请求并将同一请求转发给 继承人 对象。

    有用的SE问题/文章:

    Why would I ever use a Chain of Responsibility over a Decorator?

    Common usages for chain of responsibility?

    chain-of-responsibility-pattern 从OODE设计

    chain_of_responsibility 源代码生成