首页 文章

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

提问于
浏览
342

我正在设计一个简单的基于Web的应用程序 . 我是这个基于Web的域的新手 . 我需要您对设计模式的建议,例如如何在Servlet中分配责任,制作新Servlet的标准等 .

实际上,我的主页上有很少的实体,并且每个实体对应于我们的添加,编辑和删除等几个选项 . 之前我每个选项使用一个Servlet,如Servlet1,用于添加entity1,Servlet2用于编辑entity1等等,这样我们最终得到了大量的servlet .

现在我们正在改变我们的设计 . 我的问题是你如何确切地选择如何选择servlet的责任 . 我们是否应该为每个实体安装一个Servlet,它将处理所有选项并将请求转发给服务层 . 或者我们应该为整个页面都有一个servlet来处理整个页面请求,然后将其转发到相应的服务层?此外,请求对象是否应转发到服务层 .

5 回答

  • 474

    在被打败的MVC模式中,Servlet是“C” - 控制器 .

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

    但是,我不会开始编写原始 Servlet 类 . 他们所做的工作是非常可预测的和样板,这个框架非常好 . 幸运的是,有许多可用的,经过时间考验的候选人(按字母顺序排列):Apache WicketJava Server FacesSpring仅举几例 .

  • 1

    BalusC出色的答案涵盖了Web应用程序的大多数模式 .

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

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

    Use case to use this pattern:

    处理请求(命令)的处理程序未知时,可以将此请求发送到多个对象 . 通常,您将后继设置为对象 . 如果当前对象无法处理请求或部分处理请求并将相同的请求转发给后继对象 .

    Useful SE questions/articles:

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

    Common usages for chain of responsibility?

    chain-of-responsibility-pattern来自oodesign

    来自sourcemaking的chain_of_responsibility

  • 3

    有点像样的Web应用程序由多种设计模式组成 . 我只会提到最重要的一些 .


    模型视图控制器模式

    您想要使用的核心(架构)设计模式是Model-View-Controller pattern . Controller由Servlet表示,Servlet根据请求直接创建/使用特定的Model和View . 该模型由Javabean类表示 . 这通常在商业模型中可以进一步分类,其中包含动作(行为)和包含数据(信息)的数据模型 . View将由JSP文件表示,这些文件可通过EL(表达式语言)直接访问(数据)模型 .

    然后,根据如何处理操作和事件,存在变化 . 流行的是:

    • Request (action) based MVC :这是最简单的实现方式 . (业务)模型直接与 HttpServletRequestHttpServletResponse 对象一起使用 . 您必须自己收集,转换和验证请求参数(主要是) . View可以用普通的HTML / CSS / JS表示,它不会跨请求维护状态 . 这是其中Spring MVCStrutsStripes的工作原理 .

    • Component based MVC :这很难实现 . 但最终会得到一个更简单的模型和视图,其中所有的"raw" Servlet API都被完全抽象出来 . 您不应该自己收集,转换和验证请求参数 . Controller执行此任务并在模型中设置收集,转换和验证的请求参数 . 您需要做的就是定义直接与模型属性一起使用的操作方法 . View由"components"表示,其中包含JSP标签库或XML元素,后者又生成HTML / CSS / JS . 后续请求的View状态在会话中维护 . 这对于服务器端转换,验证和值更改事件特别有用 . 这是其中JSFWicketPlay!的工作原理 .

    作为旁注,使用自行开发的MVC框架进行游戏是一项非常好的学习练习,只要您将其保留用于个人/私人目的,我就会推荐它 . 但是一旦你专业,那么强烈建议选择现有的框架,而不是重新发明自己的框架 . 学习现有的和完善的框架需要长期的时间,而不是发展和自己维护一个强大的框架 .

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


    前端控制器模式(中介模式)

    首先,Controller部分应该实现Front Controller pattern(这是一种特殊的Mediator pattern) . 它应该只包含一个servlet,它提供所有请求的集中入口点 . 它应该基于请求可用的信息创建模型,例如pathinfo或servletpath,方法和/或特定参数 . 商业模型在以下HttpServlet示例中称为 Action .

    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映射到 web.xml 中的特定 url-pattern ,例如 /pages/**.do 甚至只是 *.html .

    在前缀模式的情况下,例如 /pages/* ,您可以调用URL,如http://example.com/pages/registerhttp://example.com/pages/login等,并提供 /WEB-INF/register.jsp/WEB-INF/login.jsp 以及相应的GET和POST操作 . 然后 registerlogin 等部件可以通过request.getPathInfo()获得,如上例所示 .

    当您使用后缀模式(如 *.do*.html 等)时,您可以调用URL,如http://example.com/register.dohttp://example.com/login.do等,您应该更改此答案中的代码示例(也是 ActionFactory )以提取 registerlogin 部分request.getServletPath()而不是 .


    战略模式

    Action 应该遵循Strategy pattern . 它需要被定义为一个抽象/接口类型,它应该根据抽象方法的传入参数来完成工作(这与Command pattern的区别在于,抽象/接口类型应该根据参数进行工作这些是在实施过程中传入的 .

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

    您可能希望使用 ActionException 等自定义异常使 Exception 更具体 . 这只是一个基本的启动示例,其余的全部取决于您 .

    这是一个 LoginAction 的例子(正如其名称所示)登录用户 . User 本身又是一个数据模型 . View知道 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.
            }
        }
    
    }
    

    工厂方法模式

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

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

    actions 反过来应该是一些静态/应用范围 Map<String, Action> ,其中包含所有已知的操作 . 这取决于你如何填写这张 Map . 硬编码:

    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());
        }
    }
    

    请记住,在没有映射的情况下创建"do nothing" Action . 比如让它直接返回 request.getPathInfo().substring(1) 然后 .


    其他模式

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

    为了更进一步,您可以使用Facade pattern创建一个 Context 类,该类反过来包装请求和响应对象,并提供委托给请求和响应对象的几个便捷方法,并将其作为参数传递到 Action#execute() 方法中 . 这会添加一个额外的抽象层来隐藏原始的Servlet API . 那么你应该在每个 Action 实现中最终得到 zero import javax.servlet.* 声明 . 在JSF术语中,这就是FacesContextExternalContext类正在做的事情 . 您可以在this answer中找到具体示例 .

    然后就是State pattern,在这种情况下你想要添加一个额外的抽象层来分割收集请求参数,转换它们,验证它们,更新模型值和执行动作的任务 . 在JSF术语中,这就是LifeCycle正在做的事情 .

    然后是Composite pattern,对于您想要创建基于组件的视图的情况,该视图可以与模型连接,其行为取决于基于请求的生命周期的状态 . 在JSF术语中,这是UIComponent代表的内容 .

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


    另见:

  • 9

    我使用了struts框架,发现它相当容易学习 . 使用struts框架时,您网站的每个页面都将包含以下项目 .

    1)每次刷新HTML页面时都会调用一个使用的动作 . 首次加载页面时,操作应填充表单中的数据,并处理Web UI和Web UI之间的交互业务层 . 如果您使用jsp页面修改可变java对象,则应将java对象的副本存储在表单而不是原始对象中,以便除非用户保存页面,否则不会修改原始数据 .

    2)用于在动作和jsp页面之间传输数据的表单 . 此对象应包含一组getter和setter,用于需要jsp文件可访问的属性 . 该表单还有一种方法可以在数据被持久化之前对其进行验证 .

    3)一个jsp页面,用于呈现页面的最终HTML . jsp页面是HTML和特殊struts标签的混合体,用于访问和操作表单中的数据 . 尽管struts允许用户将Java代码插入到jsp文件中,但您应该非常谨慎,因为它会使您的代码更难以阅读 . jsp文件中的Java代码很难调试,无法进行单元测试 . 如果您发现自己在jsp文件中编写了超过4-5行java代码,则代码应该可以移动到该操作 .

  • 1

    恕我直言,如果你从责任分配的角度来看它,在网络应用的情况下没有太大的区别 . 但是,要保持图层的清晰度 . 在表示层中保留任何纯粹用于演示目的的内容,例如特定于Web控件的控件和代码 . 只需将您的实体保留在业务层中,并在业务层中保留所有功能(如添加,编辑,删除等) . 但是将它们渲染到要在表示层中处理的浏览器上 . 对于.Net,ASP.NET MVC模式在保持层分离方面非常好 . 看看MVC模式 .

相关问题