java - para - Patrones de diseño basados en aplicaciones web
patrones de diseño para aplicaciones web (5)
En el patrón MVC golpeado, el Servlet es "C" - controlador.
Su trabajo principal es realizar la evaluación de la 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 este motivo, debe pasar el objeto de solicitud a la capa de servicio.
Sin embargo, no empezaría a escribir clases de Servlet
procesar. El trabajo que hacen es muy predecible y repetitivo, algo que el marco hace muy bien. Afortunadamente, hay muchos candidatos disponibles, probados en el tiempo (en orden alfabético): Apache Wicket , Java Server Faces , Spring , por nombrar algunos.
Estoy diseñando una aplicación sencilla basada en web. Soy nuevo en este dominio basado en la web. Necesitaba su consejo sobre los patrones de diseño, como la forma en que se debe distribuir la responsabilidad entre los Servlets, los criterios para crear nuevos Servlets, etc.
En realidad, tengo pocas entidades en mi página de inicio y, para cada una de ellas, tenemos pocas opciones como agregar, editar y eliminar. Anteriormente estaba usando un Servlet por opciones como Servlet1 para agregar entity1, Servlet2 para editar entity1 y así sucesivamente, 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. Si tuviéramos 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á la solicitud de toda la página y luego la reenviaremos a la capa de servicio correspondiente? Además, debe el objeto de solicitud reenviado a la capa de servicio o no.
En mi humilde opinión, no hay mucha diferencia en el caso de la aplicación web si lo mira desde el ángulo de asignación de responsabilidad. Sin embargo, mantener la claridad en la capa. Mantenga cualquier cosa con el único fin de la presentación en la capa de presentación, como el control y el código específico de los controles web. Solo mantenga sus entidades en la capa empresarial y todas las funciones (como agregar, editar, eliminar), etc. en la capa empresarial. Sin embargo, procesarlos en el navegador para que se manejen en la capa de presentación. Para .Net, el patrón MVC de ASP.NET es muy bueno en términos de mantener las capas separadas. Mira en el patrón MVC.
He utilizado el marco de struts y me resulta bastante fácil de aprender. Al usar 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 consistir en un conjunto de captadores y definidores para atributos que deben ser accesibles al archivo jsp. El formulario también tiene un método para validar los datos antes de que se conserve.
3) Una página jsp que se utiliza 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 los datos en el formulario. Aunque los struts permiten 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 puede probarse por unidades. 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.
Una aplicación web bastante decente consiste en una mezcla de patrones de diseño. Mencionaré solo las más importantes.
Modelo de controlador de vista
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 / utiliza directamente un Modelo y Vista específicos basados en la solicitud. El modelo será representado por las clases javabeanas. Esto es a menudo 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 debe estar representada por archivos JSP que tengan acceso directo al modelo (de datos ) por EL (lenguaje de expresión).
Luego, hay variaciones basadas en cómo se manejan las acciones y los eventos. Los más populares son:
Solicitud (acción) basada en MVC : este es el más simple de implementar. El modelo ( comercial ) funciona directamente con los objetos
HttpServletRequest
yHttpServletResponse
. Debe reunir, convertir y validar los parámetros de solicitud (en su mayoría) usted mismo. La vista puede representarse mediante un simple código HTML / CSS / JS y no mantiene el estado en todas las solicitudes. Así es como funciona Spring MVC , Struts and Stripes .Componente basado en MVC : 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 "sin procesar" se extrae completamente. No debería tener la necesidad de recopilar, 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 trabajen directamente con las propiedades del modelo. La vista está representada por "componentes" en forma de etiquetas de código JSP o elementos XML que a su vez generan HTML / CSS / JS. El estado de la vista para las solicitudes posteriores se mantiene en la sesión. Esto es particularmente útil para los eventos de conversión, validación y cambio de valor del lado del servidor. ¡Así es como entre otros JSF , Wicket y Play! trabajos.
Como nota al margen, pasear por ahí con un marco MVC de cosecha propia es un ejercicio de aprendizaje muy agradable, y lo recomiendo siempre que lo guarde 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 lleva a largo plazo menos tiempo que desarrollar y mantener un marco sólido.
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 del controlador frontal ( patrón del mediador )
Primero, la parte del controlador debe implementar el patrón del controlador frontal (que es un tipo especializado de patrón de mediador ). Debería constar de un solo servlet que proporcione un punto de entrada centralizado de todas las solicitudes. Debe crear el modelo en función de la información disponible por la solicitud, como la ruta de información de ruta de acceso o servlet, el método y / o los parámetros específicos. El Modelo de Negocio se llama Action
en el siguiente ejemplo de 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);
}
}
La ejecución de la acción debe devolver algún identificador para localizar la vista. Lo más simple sería usarlo como nombre de archivo de la JSP. Asigne este servlet a un url-pattern
específico en web.xml
, por ejemplo, /pages/*
, *.do
o incluso *.html
.
En el caso de patrones de prefijo, como por ejemplo /pages/*
, podría invocar URL como http://example.com/pages/register , http://example.com/pages/login , etc. y proporcionar /WEB-INF/register.jsp
, /WEB-INF/login.jsp
con las acciones GET y POST apropiadas. Las partes de register
, login
, etc. están disponibles por request.getPathInfo()
como en el ejemplo anterior.
Cuando utiliza patrones de sufijo como *.do
, *.html
, etc., puede invocar URL como http://example.com/register.do , http://example.com/login.do , etc. y debe cambiar los ejemplos de código en esta respuesta (también ActionFactory
) para extraer las partes de register
e login
mediante request.getServletPath()
lugar.
Patrón de estrategia
La Action
debe seguir el patrón de la Estrategia . Debe definirse como un tipo de interfaz / resumen que debe hacer el trabajo en función de los argumentos pasados del método abstracto (esta es la diferencia con el patrón de Comando , en el que el tipo de interfaz / resumen debe hacer el trabajo en función del 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 que la Exception
más específica con una excepción personalizada como ActionException
. Es solo un ejemplo básico de arranque, el resto depende de usted.
Este es un ejemplo de una LoginAction
que (como su nombre lo indica) inicia sesión en el usuario. El propio User
es a su vez un modelo de datos . La Vista es consciente de la presencia del 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 ActionFactory
debe seguir el patrón de método de fábrica . Básicamente, debería proporcionar un método creativo que devuelva una implementación concreta de un tipo de interfaz / resumen. En este caso, debe devolver una implementación de la interfaz de Action
basada en la información proporcionada por la solicitud. Por ejemplo, el method y la información de request.getPathInfo() (la información de ruta es la parte posterior a la ruta de contexto y servlet en la URL de solicitud, excluyendo la cadena de consulta).
public static Action getAction(HttpServletRequest request) {
return actions.get(request.getMethod() + request.getPathInfo());
}
Las actions
a su vez deben ser un Map<String, Action>
estático / de toda la aplicación Map<String, Action>
que contiene 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 según un archivo de configuración de propiedades / XML en la ruta de clase: (pseudo)
for (Entry entry : configuration) {
actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
}
O dinámicamente, basado en un escaneo en la ruta de clase para las 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());
}
}
Tenga en cuenta que para crear una Action
"no hacer nada" en el caso de que no haya mapeo. Deje que, por ejemplo, devuelva directamente request.getPathInfo().substring(1)
luego.
Otros patrones
Esos eran los patrones importantes hasta ahora.
Para ir un paso más allá, puede usar el patrón de fachada para crear una clase de Context
que a su vez envuelve los objetos de solicitud y respuesta y ofrece varios métodos convenientes para delegar a los objetos de solicitud y respuesta y pasar ese argumento a la Action#execute()
método en su lugar. Esto agrega una capa abstracta adicional para ocultar la API de Servlet sin procesar. Básicamente, debería terminar con declaraciones de import javax.servlet.*
En cada implementación de Action
. En términos de JSF, esto es lo que hacen las clases FacesContext
y ExternalContext
. Puedes encontrar un ejemplo concreto en esta respuesta .
Luego está el patrón de estado para el caso en el que le gustaría 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 está haciendo el LifeCycle
.
Luego está el patrón compuesto para el caso en el que le gustaría crear una vista basada en componentes que se pueda adjuntar con el modelo y cuyo comportamiento dependa del estado del ciclo de vida basado en la solicitud. En términos JSF, esto es lo que representa el UIComponent
.
De esta manera puede evolucionar poco a poco hacia un marco basado en componentes.
Ver también:
La excelente respuesta de BalusC cubre la mayoría de los patrones para aplicaciones web.
Algunas aplicaciones pueden requerir un Chain-of-responsibility_pattern
En el diseño orientado a objetos, el patrón de cadena de responsabilidad es un patrón de diseño que consiste en 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.
Utilice el caso para utilizar este patrón:
Cuando el controlador para procesar una solicitud (comando) es desconocido y esta solicitud puede enviarse a varios objetos. En general, el sucesor se establece 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 de SE útiles:
¿Por qué alguna vez usaría una Cadena de Responsabilidad sobre un Decorador?
Usos comunes para la cadena de responsabilidad?
chain-of-responsibility-pattern de oodesign
chain_of_responsibility de la fuente