基于Web的Design Patterns应用程序[关闭]


359

我正在设计一个简单的基于Web的应用程序。我是这个基于Web的领域的新手,我需要您提供有关设计模式的建议,例如如何在Servlet之间分配责任,创建新Servlet的标准等。

实际上,我主页上的实体很少,而与每个实体相对应,我们几乎没有添加,编辑和删除等选项。早些时候,我对每个选项使用一个Servlet,例如Servlet1用于添加实体1,Servlet2用于编辑实体1,依此类推,这样我们最终拥有大量的Servlet。

现在,我们正在更改设计。我的问题是,如何正确选择如何选择servlet的责任。每个实体是否应该有一个Servlet,它将处理所有选项并将请求转发到服务层。还是应该为整个页面设置一个servlet,它将处理整个页面请求,然后将其转发到相应的服务层?同样,请求对象是否应该转发到服务层。


8
并不是真正的官方设计模式,但不要忘了PRG(重定向后获取)和Hijax(首先不使用js进行工作,然后使用ajax劫持链接和按钮)
Neil McGuigan

Answers:


488

有点像样的Web应用程序包含多种设计模式。我只提到最重要的那些。


模型视图控制器模式

您要使用的核心(架构)设计模式是Model-View-Controller模式。该控制器是由一个Servlet其中(在)直接创造来表示/使用特定的模型视图基于该请求。该模型将由Javabean类表示。在包含动作(行为)的业务模型和包含数据(信息)的数据模型中,这通常可以进一步划分。该视图是由具有对(直接访问JSP文件来表示数据模型由EL(表达式语言)。

然后,根据操作和事件的处理方式而有所不同。最受欢迎的是:

  • 基于请求(动作)的MVC:这是最简单的实现。(业务模型直接与HttpServletRequestHttpServletResponse对象一起使用。您必须自己(主要)收集,转换和验证请求参数。该视图可以用普通的HTML / CSS / JS表示,并且不会在请求中保持状态。这就是Spring MVCStrutsStripes的工作方式。

  • 基于组件的MVC:这很难实现。但是最终您得到了一个更简单的模型和视图,其中所有“原始” Servlet API都被完全抽象了。您不需要自己收集,转换和验证请求参数。所述控制器执行此任务,并设置在所收集的,转换后的和验证请求参数模型。您需要做的就是定义直接与模型属性一起使用的操作方法。该视图是通过“组件”在JSP标记库或依次产生HTML / CSS / JS的XML元素的风味表示。视图的状态在会话中维护后续请求。这对于服务器端转换,验证和值更改事件特别有用。这就是JSFWicketPlay的方式!作品。

附带说明,使用本地MVC框架是一个非常不错的学习方法,只要您出于个人/私人目的保留它,我建议您这样做。但是一旦您成为专业人士,则强烈建议您选择一个现有的框架,而不要重塑自己的框架。学习现有的和完善的框架要比自己开发和维护一个健壮的框架花费更少的时间。

在下面的详细说明中,我将自己限制为基于请求的MVC,因为它更易于实现。


前控制器模式调解器模式

首先,Controller部分应实现Front Controller模式(这是一种专门的Mediator模式)。它应该仅包含一个servlet,该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-patternweb.xml,例如/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应遵循的策略模式。需要将其定义为抽象/接口类型,该类型应基于抽象方法的传入参数来完成工作(这与Command模式不同,其中命令 /抽象类型应基于在创建实现过程中传入的参数)。

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

您可能需要Exception使用像这样的自定义异常进行更具体的说明ActionException。这只是一个基本的启动示例,其余的全由您决定。

这是一个LoginAction(如其名称所示)登录用户的示例。该User本身又一个数据模型。该视图是知道的存在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应遵循工厂方法模式。基本上,它应该提供一种创建方法,该方法返回抽象/接口类型的具体实现。在这种情况下,它应该Action根据请求提供的信息返回接口的实现。例如,方法pathinfo(pathinfo是请求URL中上下文和servlet路径之后的部分,不包括查询字符串)。

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

actions反过来应该是一些静态/应用程序范围Map<String, Action>它包含所有已知的行动。如何填写这张地图由您决定。硬编码:

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

Action没有映射的情况下,请记住创建“不执行任何操作” 。让它例如直接返回request.getPathInfo().substring(1)then。


其他图案

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

要更进一步,您可以使用Facade模式创建一个Context类,该类依次包装请求和响应对象,并提供几个委托给请求和响应对象的便捷方法,并将其作为参数传递给该Action#execute()方法。这增加了一个额外的抽象层以隐藏原始Servlet API。然后,您基本上应该在每个实现中都以 import javax.servlet.*声明结束Action。用JSF术语,这是FacesContextExternalContext类的作用。您可以在此答案中找到一个具体示例。

然后是一种状态模式,您想添加一个额外的抽象层来拆分收集请求参数,转换请求,验证请求,更新模型值和执行操作的任务。用JSF术语,这LifeCycle就是这样做的。

然后是针对您要创建基于组件的视图的Composite模式,该视图可以随模型一起附加,其行为取决于基于请求的生命周期的状态。用JSF术语UIComponent表示。

这样,您可以一点一点地朝着基于组件的框架发展。


也可以看看:


4
@masato:您可以在例如静态初始化程序块中执行此操作。
BalusC 2010年

1
@masato:顺便说一句,如果您想从中检索它们web.xml,则可以ServletContextListener为此使用a 。他们的厂实现它(并注册为<listener>web.xml),并在做填充的工作contextInitialized()方法。
BalusC

3
改为执行“ post_servlet”应在操作中执行的工作。您不应有多个servlet。业务内容应在动作课中完成。如果您希望它是一个新请求,请返回另一个视图,该视图将导致重定向并执行与GET请求关联的新操作中的工作。
BalusC 2010年

2
依靠。最简单的方法就是在Action实现中以与普通servlet相同的方式正确地执行此操作(有关基本示例,另请参阅servlet wiki,您可以随意将其进一步重构为某些Validator接口)。但是您也可以在调用操作之前执行此操作,但这更加复杂,因为它需要基于每个视图知道验证规则。JSF还通过提供涵盖本required="true"validator="customValidatorName"等在XHTML标记。
BalusC 2012年

2
@AndreyBotalov:检查MVC框架(如JSF,Spring MVC,Wicket,Struts2等)的源代码。它们都是开源的。
BalusC 2012年

13

在传统的MVC模式中,Servlet是“ C”-控制器。

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

不过,我不会开始编写原始Servlet类。他们所做的工作是非常可预测的,并且很容易完成,而框架做得很好。幸运的是,有许多可用的,经过时间考验的候选人(按字母顺序排列):Apache WicketJava Server FacesSpring等


5

恕我直言,从责任分配的角度来看,Web应用程序的区别不大。但是,请保持层中的清晰度。纯粹出于表示目的,将任何内容保留在表示层中,例如控件和特定于Web控件的代码。只需将您的实体保留在业务层中,并将所有功能(如添加,编辑,删除)保留在业务层中即可。但是,将它们渲染到浏览器中以在表示层中进行处理。对于.Net,ASP.NET MVC模式在保持层分离方面非常好。查看MVC模式。


您可以对servlet应该包含的内容进行一些明确说明吗?
mawia 2010年

如果使用MVC,则servlet应该是控制器。
康坎

3

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

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

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

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


注意:在struts 2中,Form对象被称为Model,但是其工作方式与我在原始答案中描述的相同。
EsotericNonsense

3

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

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

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

用例使用此模式:

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

有用的SE问题/文章:

为什么我会在装饰者身上使用责任链?

责任链的常见用法?

oodesign 的责任链模式

来源链的责任链

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.