Diseñar aplicaciones web basadas en patrones [cerrado]

359

Estoy diseñando una aplicación simple basada en la web. Soy nuevo en este dominio basado en la web. Necesitaba su consejo con respecto a los patrones de diseño, como cómo se debe distribuir la responsabilidad entre los Servlets, los criterios para hacer nuevos Servlet, etc.

En realidad, tengo pocas entidades en mi página de inicio y, en correspondencia con cada una de ellas, tenemos pocas opciones como agregar, editar y eliminar. Anteriormente estaba usando un Servlet por opciones como Servlet1 para agregar entidad1, Servlet2 para editar entidad1, etc. y de esta manera terminamos teniendo una gran cantidad de servlets.

Ahora estamos cambiando nuestro diseño. Mi pregunta es cómo elige exactamente cómo elige la responsabilidad de un servlet. Deberíamos tener un Servlet por entidad que procesará todas sus opciones y reenviará la solicitud a la capa de servicio. ¿O deberíamos tener un servlet para toda la página que procesará toda la solicitud de la página y luego la reenviará a la capa de servicio correspondiente? Además, si el objeto de solicitud se reenvía a la capa de servicio o no.

mawia
fuente
8
No son patrones de diseño realmente oficiales, pero no olvides PRG (post-redirect-get) e Hijax (haz el trabajo sin js primero, luego secuestra los enlaces y botones con ajax)
Neil McGuigan

Respuestas:

489

Una aplicación web un poco decente consiste en una mezcla de patrones de diseño. Mencionaré solo los más importantes.


Modelo de controlador de vista de modelo

El patrón de diseño central (arquitectónico) que le gustaría usar es el patrón Modelo-Vista-Controlador . El Controlador debe estar representado por un Servlet que (in) crea / usa directamente un Modelo y Vista específicos basados ​​en la solicitud. El modelo se representará mediante clases Javabean. Esto a menudo es más divisible en el Modelo de Negocio que contiene las acciones (comportamiento) y el Modelo de Datos que contiene los datos (información). La vista se representará con archivos JSP que tengan acceso directo al modelo ( datos ) mediante EL (lenguaje de expresión).

Luego, hay variaciones basadas en cómo se manejan las acciones y los eventos. Los populares son:

  • MVC basado en solicitud (acción) : este es el más sencillo de implementar. El modelo ( comercial ) trabaja directamente con y objetos. Debe reunir, convertir y validar los parámetros de solicitud (principalmente) usted mismo. La Vista se puede representar mediante HTML / CSS / JS simple y no mantiene el estado en todas las solicitudes. Así es como funciona Spring MVC , Struts and Stripes , entre otros .HttpServletRequestHttpServletResponse

  • MVC basado en componentes : esto es más difícil de implementar. Pero terminas con un modelo y una vista más simples en los que toda la API de Servlet "en bruto" se abstrae completamente. No debería tener la necesidad de reunir, convertir y validar los parámetros de solicitud usted mismo. El controlador realiza esta tarea y establece los parámetros de solicitud recopilados, convertidos y validados en el modelo . Todo lo que necesita hacer es definir métodos de acción que funcionen directamente con las propiedades del modelo. La Vista está representada por "componentes" en forma de taglibs JSP o elementos XML que a su vez generan HTML / CSS / JS. El estado de la vistapara las solicitudes posteriores se mantiene en la sesión. Esto es particularmente útil para la conversión del lado del servidor, la validación y los eventos de cambio de valor. ¡Así es como, entre otros , JSF , Wicket y Play! trabajos.

Como nota al margen, pasatiempo con un marco MVC de cosecha propia es un ejercicio de aprendizaje muy agradable, y lo recomiendo siempre que lo conserve para fines personales / privados. Pero una vez que sea profesional, se recomienda encarecidamente elegir un marco existente en lugar de reinventar el suyo. Aprender un marco existente y bien desarrollado requiere a largo plazo menos tiempo que desarrollar y mantener un marco robusto usted mismo.

En la explicación detallada a continuación, me limitaré a solicitar MVC basado, ya que es más fácil de implementar.


Patrón de controlador frontal ( patrón de mediador )

Primero, la parte Controlador debe implementar el patrón Controlador frontal (que es un tipo especializado de patrón Mediador ). Debe consistir en un solo servlet que proporcione un punto de entrada centralizado para todas las solicitudes. Debe crear el Modelo en función de la información disponible por la solicitud, como la información de ruta o la ruta de servlet, el método y / o parámetros específicos. El modelo de negocio se llama Actionen el siguiente HttpServletejemplo.

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

La ejecución de la acción debería devolver algún identificador para ubicar la vista. Lo más sencillo sería usarlo como nombre de archivo del JSP. Asigne este servlet en un específico url-patternen web.xml, por ejemplo /pages/*, *.doo incluso simplemente *.html.

En el caso de patrones de prefijos como, por ejemplo /pages/*, puede invocar URL como http://example.com/pages/register , http://example.com/pages/login , etc. y proporcionar /WEB-INF/register.jsp, /WEB-INF/login.jspcon las acciones GET y POST apropiadas . Las partes register, loginetc. están disponibles request.getPathInfo()como en el ejemplo anterior.

Cuando utiliza patrones de sufijos como *.do, *.htmletc., puede invocar URL como http://example.com/register.do , http://example.com/login.do , etc. y debe cambiar el ejemplos de código en esta respuesta (también el ActionFactory) para extraer las partes registery loginen su request.getServletPath()lugar.


Patrón de estrategia

El Actiondebe seguir el patrón de estrategia . Debe definirse como un tipo abstracto / interfaz que debe hacer el trabajo basado en los argumentos pasados del método abstracto (esta es la diferencia con el patrón de Comando , en el que el tipo abstracto / interfaz debe hacer el trabajo basado en el argumentos que se han pasado durante la creación de la implementación).

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

Es posible que desee hacer el Exceptionmás específico con una excepción personalizada como ActionException. Es solo un ejemplo básico de inicio, el resto depende de usted.

Aquí hay un ejemplo de un LoginActionque (como su nombre lo indica) inicia sesión en el usuario. El Usermismo es a su vez un modelo de datos . La Vista es consciente de la presencia de 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.
        }
    }

}

Patrón de método de fábrica

El ActionFactorydebe seguir el patrón del método de fábrica . Básicamente, debe proporcionar un método de creación que devuelva una implementación concreta de un tipo abstracto / interfaz. En este caso, debería devolver una implementación de la Actioninterfaz basada en la información proporcionada por la solicitud. Por ejemplo, el método y la información de ruta (la información de ruta es la parte posterior al contexto y la ruta de servlet en la URL de solicitud, excluyendo la cadena de consulta).

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

A actionssu vez, debe ser algo estático / en toda la aplicación Map<String, Action>que contenga todas las acciones conocidas. Depende de usted cómo llenar este mapa. Código difícil:

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

O configurable en función de un archivo de configuración de propiedades / XML en el classpath: (pseudo)

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

O basado dinámicamente en un escaneo en el classpath para clases que implementan una determinada interfaz y / o anotación: (pseudo)

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

Recuerde crear un "no hacer nada" Actionpara el caso de que no haya mapeo. Deje que, por ejemplo, devuelva directamente el request.getPathInfo().substring(1)entonces.


Otros patrones

Esos fueron los patrones importantes hasta ahora.

Para ir un paso más allá, puede usar el patrón Fachada para crear una Contextclase que, a su vez, envuelva los objetos de solicitud y respuesta y ofrezca varios métodos convenientes para delegar en los objetos de solicitud y respuesta y pasar eso como argumento al Action#execute()método. Esto agrega una capa abstracta adicional para ocultar la API de Servlet sin procesar. Básicamente, debería terminar con cero import javax.servlet.* declaraciones en cada Actionimplementación. En términos de JSF, esto es lo que están haciendo las clases FacesContexty ExternalContext. Puedes encontrar un ejemplo concreto en esta respuesta .

Luego está el patrón de Estado para el caso en el que desea agregar una capa de abstracción adicional para dividir las tareas de recopilar los parámetros de solicitud, convertirlos, validarlos, actualizar los valores del modelo y ejecutar las acciones. En términos de JSF, esto es lo que LifeCycleestá haciendo.

Luego está el patrón compuesto para el caso en el que desea crear una vista basada en componentes que se puede adjuntar con el modelo y cuyo comportamiento depende del estado del ciclo de vida basado en la solicitud. En términos de JSF, esto es lo que UIComponentrepresentan.

De esta manera, puede evolucionar poco a poco hacia un marco basado en componentes.


Ver también:

BalusC
fuente
44
@masato: podría hacer esto, por ejemplo, en un bloque inicializador estático.
BalusC
1
@masato: por cierto, si desea recuperarlos web.xml, entonces podría usar un ServletContextListenerpara esto. Haga que la fábrica lo implemente (y se registre como <listener>en web.xml) y haga el trabajo de llenado durante el contextInitialized()método.
BalusC
3
Realice el trabajo que el "post_servlet" debería hacer en la acción. No deberías tener más de un servlet. Las cosas de negocios deben hacerse en clases de acción. Si desea que sea una nueva solicitud, vuelva a una vista diferente que provocaría una redirección y haga el trabajo en la nueva acción asociada con la solicitud GET.
BalusC
2
Depende Lo más fácil es hacerlo correctamente en la Actionimplementación de la misma manera que con los servlets normales (vea también la wiki de servlets para un ejemplo básico, que puede refactorizar aún más en alguna Validatorinterfaz). Pero también puede hacerlo antes de invocar la acción, pero esto es más complejo ya que requiere que las reglas de validación se conozcan por vista. JSF ha cubierto por esta oferta required="true", validator="customValidatorName", etc en el código XHTML.
BalusC
2
@AndreyBotalov: verifique el código fuente de los marcos MVC como JSF, Spring MVC, Wicket, Struts2, etc. Todos son de código abierto.
BalusC
13

En el patrón MVC golpeado, el Servlet es "C" - controlador.

Su trabajo principal es hacer una evaluación de solicitud inicial y luego enviar el procesamiento basado en la evaluación inicial al trabajador específico. Una de las responsabilidades del trabajador puede ser configurar algunos beans de capa de presentación y reenviar la solicitud a la página JSP para representar HTML. Entonces, solo por esta razón, debe pasar el objeto de solicitud a la capa de servicio.

Sin embargo, no comenzaría a escribir Servletclases crudas . El trabajo que hacen es muy predecible y repetitivo, algo que el marco hace muy bien. Afortunadamente, hay muchos candidatos disponibles y probados (en orden alfabético): Apache Wicket , Java Server Faces , Spring , por nombrar algunos.

Alexander Pogrebnyak
fuente
5

En mi humilde opinión, no hay mucha diferencia en el caso de la aplicación web si lo miras desde el ángulo de la asignación de responsabilidad. Sin embargo, mantenga la claridad en la capa. Guarde cualquier cosa exclusivamente para el propósito de la presentación en la capa de presentación, como el control y el código específico de los controles web. Simplemente mantenga sus entidades en la capa empresarial y todas las características (como agregar, editar, eliminar), etc. en la capa empresarial. Sin embargo, presentarlos en el navegador para ser manejados en la capa de presentación. Para .Net, el patrón ASP.NET MVC es muy bueno en términos de mantener las capas separadas. Mira en el patrón MVC.

Kangkan
fuente
¿puedes ir un poco explícito en lo que debería ir en servlet?
mawia
El servlet debe ser el controlador si usa MVC.
Kangkan
3

He utilizado el marco de struts y me resulta bastante fácil de aprender. Cuando utilice el marco de struts, cada página de su sitio tendrá los siguientes elementos.

1) Se llama a una acción que se utiliza cada vez que se actualiza la página HTML. La acción debe llenar los datos en el formulario cuando la página se carga por primera vez y maneja las interacciones entre la interfaz de usuario web y la capa empresarial. Si está utilizando la página jsp para modificar un objeto Java mutable, se debe almacenar una copia del objeto Java en el formulario en lugar del original para que los datos originales no se modifiquen a menos que el usuario guarde la página.

2) El formulario que se utiliza para transferir datos entre la acción y la página jsp. Este objeto debe constar de un conjunto de captadores y definidores de atributos que deben ser accesibles para el archivo jsp. El formulario también tiene un método para validar los datos antes de que persistan.

3) Una página jsp que se usa para representar el HTML final de la página. La página jsp es un híbrido de HTML y etiquetas de struts especiales que se utilizan para acceder y manipular datos en el formulario. Aunque Struts permite a los usuarios insertar código Java en archivos jsp, debe tener mucho cuidado al hacerlo porque hace que su código sea más difícil de leer. El código Java dentro de los archivos jsp es difícil de depurar y no se puede probar en la unidad. Si se encuentra escribiendo más de 4-5 líneas de código java dentro de un archivo jsp, el código probablemente debería moverse a la acción.

Sin sentido esotérico
fuente
Nota: En los struts 2, el objeto Form se denomina Modelo, pero funciona de la misma manera que describí en mi respuesta original.
EsotericNonsense
3

La excelente respuesta de BalusC cubre la mayoría de los patrones para aplicaciones web.

Algunas aplicaciones pueden requerir un patrón de cadena de responsabilidad

En el diseño orientado a objetos, el patrón de cadena de responsabilidad es un patrón de diseño que consta de una fuente de objetos de comando y una serie de objetos de procesamiento. Cada objeto de procesamiento contiene lógica que define los tipos de objetos de comando que puede manejar; el resto se pasa al siguiente objeto de procesamiento en la cadena.

Caso de uso para usar este patrón:

Cuando se desconoce el controlador para procesar una solicitud (comando) y esta solicitud se puede enviar a varios objetos. Generalmente establece sucesor para objetar. Si el objeto actual no puede manejar la solicitud o procesar la solicitud parcialmente y reenviar la misma solicitud al objeto sucesor .

Preguntas / artículos útiles sobre SE:

¿Por qué usaría una cadena de responsabilidad sobre un decorador?

Usos comunes para la cadena de responsabilidad?

patrón de cadena de responsabilidad de oodesign

cadena_de_responsabilidad de la creación de código fuente

Ravindra babu
fuente