Patrones de diseño Java J2EE

     

    Visión general

    Este es el cuarto y último artículo de una serie corta dedicada a los patrones de diseño en Java, y una continuación directa del artículo anterior: Patrones de diseño de comportamiento en Java.

    Patrones J2EE

    Los patrones J2EE se preocupan por proporcionar soluciones relacionadas con Java EE. Estos patrones son ampliamente aceptados por otros marcos y proyectos. Como, por ejemplo: Spring.

    Los patrones J2EE que se tratan en este artículo son:

    • Patrón MVC
    • Patrón de delegado empresarial
    • Patrón de entidad compuesta
    • Patrón de objeto de acceso a datos
    • Patrón de controlador frontal
    • Patrón de filtro de interceptación
    • Patrón de localizador de servicios
    • Patrón de objeto de transferencia

    Patrón MVC

    Este es uno de los patrones más notorios y más utilizados de esta categoría. Gira en torno a la idea de Modelo-Vista-Controlador, que es de donde proviene la abreviatura.

    Modelos son básicamente objetos, o POJO para ser exactos, que se utilizan como planos / modelos para todos los objetos que se utilizarán en la aplicación.

    Puntos de vista representan el aspecto de presentación de los datos y la información ubicados en los modelos.

    Controladores controla ambos. Sirven como conexión entre los dos. Los controladores crean instancias, actualizan y eliminan modelos, los completan con información y luego envían los datos a las vistas para presentarlos al usuario final.

    Implementación

    Dicho esto, comencemos con el primero de los tres componentes de este patrón: el modelo:

    public class Employee {
        private int employeeId;
        private String name;
    
        public int getEmployeeId() {
            return employeeId;
        }
        public void setEmployeeId(int id) {
            this.employeeId = id;
        }
        public String getName() {
            return name;
        }
        public void setEmployeeName(String name) {
            this.name = name;
        }
    }
    

    Necesitamos una forma de presentar los datos del modelo, por lo que definimos una vista para ese mismo propósito:

    public class EmployeeView {
        public void printEmployeeInformation(String employeeName, int employeeId) {
            System.out.println("Employee information: ");
            System.out.println("ID: " + employeeId);
            System.out.println("Name: " + employeeName);
        }
    }
    

    La vista es responsable de formatear la información de una manera fácil de usar.

    Una vez que eso esté fuera del camino, definamos el controlador. Este controlador utilizará tanto el modelo como la vista para crear una instancia del modelo, completarlo con algunos datos y luego enviarlo a la vista para que el cliente vea:

    Te puede interesar:Patrones de diseño estructural en Java
    public class EmployeeController {
        private Employee employee;
        private EmployeeView employeeView;
    
        public EmployeeController(Employee employee, EmployeeView employeeView) {
            this.employee = employee;
            this.employeeView = employeeView;
        }
    
        public String getEmployeeName() {
            return employee.getName();
        }
        public void setEmployeeName(String name) {
            employee.setEmployeeName(name);
        }
        public int getEmployeeId() {
            return employee.getEmployeeId();
        }
        public void setEmployeeId(int id) {
            employee.setEmployeeId(id);
        }
        public void updateView() {
            employeeView.printEmployeeInformation(employee.getName(), employee.getEmployeeId());
        }
    }
    

    Con los tres componentes de este patrón completos, podemos concluir este ejemplo.

    Para ilustrar el punto de este patrón:

    public class Main {
        public static void main(String[] args) {
           Employee employee = getEmployeeFromDatabase();
           EmployeeView view = new EmployeeView();
           EmployeeController controller = new EmployeeController(employee, view);
    
           controller.updateView();
    
           controller.setEmployeeId(5);
    
           controller.updateView();
        }
    
        // simulating a database
        public static Employee getEmployeeFromDatabase() {
            Employee employee = new Employee();
            employee.setEmployeeName("David");
            employee.setEmployeeId(1);
            return employee;
        }
    }
    

    Ejecutar este fragmento de código producirá:

    Employee information:
    ID: 1
    Name: David
    Employee information:
    ID: 5
    Name: David
    

    Patrón de delegado empresarial

    El patrón Business Delegate se utiliza para desacoplar la capa de presentación de la capa empresarial para minimizar el número de solicitudes entre el cliente (presentación) y los niveles empresariales.

    Implementación

    Comencemos definiendo una interfaz para nuestros servicios comerciales:

    public interface BusinessService {
        public void process();
    }
    

    Luego, definamos dos clases concretas implementando esta interfaz:

    public class EJBService implements BusinessService {
        @Override
        public void process() {
            System.out.println("Processing using the EJB Service.");
        }
    }
    
    public class JMSService implements BusinessService {
        @Override
        public void process() {
            System.out.println("Processing using the JSM Service.");
        }
    }
    

    Definamos un servicio de búsqueda. El objeto de servicio de búsqueda debe proporcionar las implementaciones comerciales relativas y el acceso del objeto comercial a la lógica de delegado comercial:

    public class BusinessLookUp {
        public BusinessService getBusinessService(String type) {
            if (type.equalsIgnoreCase("ejb")) {
                return new EJBService();
            } else if (type.equalsIgnoreCase("JMS")) {
                return new JMSService();
            } else {
                return null;
            }
        }
    }
    

    Ahora, podemos definir nuestro delegado comercial:

    public class BusinessDelegate {
        private BusinessLookUp lookupService = new BusinessLookUp();
        private BusinessService businessService;
        private String type;
    
        public void setServiceType(String type) {
            this.type = type;
        }
    
        public void process() {
            businessService = lookupService.getBusinessService(type);
            businessService.process();
        }
    }
    

    Actúa como un punto de acceso a los servicios empresariales para el Client usar:

    public class Client {
        BusinessDelegate businessDelegate;
    
        public Client(BusinessDelegate businessDelegate) {
            this.businessDelegate = businessDelegate;
        }
    
        public void process() {
            businessDelegate.process();
        }
    }
    

    Y ahora para ilustrar el punto de este patrón:

    public class Main {
        public static void main(String[] args) {
            BusinessDelegate businessDelegate = new BusinessDelegate();
            businessDelegate.setServiceType("EJB");
    
            Client client = new Client(businessDelegate);
            client.process();
    
            businessDelegate.setServiceType("JMS");
            client.process();
        }
    }
    

    Ejecutar este fragmento de código producirá:

    Processing using the EJB Service.
    Processing using the JSM Service.
    

    Patrón de entidad compuesta

    El patrón de entidad compuesta representa un gráfico de objetos, que cuando se actualiza, activa una actualización para todas las entidades dependientes en el gráfico.

    Te puede interesar:Grafos en Java: Algoritmo de Dijkstra

    Se emplea principalmente en Enterprise JavaBeans (EJB) que no es una API muy popular ya que ha sido reemplazada por otros marcos y herramientas como la Marco de Spring y sus numerosas herramientas.

    Implementación

    Definamos dos clases cuyos datos de características necesitarían actualizar otra clase:

    public class Employee {
        private String name;
        private String jobSuccess;
    
        public void setJobSuccess(String jobSuccess) {
            this.jobSuccess = jobSuccess;
        }
    
        public String getJobSuccess() {
            return jobSuccess;
        }
    }
    
    public class Manager {
        private String name;
        private String satisfaction;
    
        public void setSatisfaction(String satisfaction) {
            this.satisfaction = satisfaction;
        }
    
        public String getSatisfaction() {
            return satisfaction;
        }
    }
    

    Si el Employee hace bien, el Manager está satisfecho y viceversa.

    Dado que el objetivo de este patrón es no permitir que los frijoles actúen como objetos «de grano fino» por sí solos, se nos presenta un objeto de grano grueso. Este objeto gestiona sus propias relaciones con otros objetos:

    public class CoarseGrainedObject {
        Employee employee = new Employee();
        Manager manager = new Manager();
    
        public void setData(String jobSuccess, String satisfaction) {
            employee.setJobSuccess(jobSuccess);
            manager.setSatisfaction(satisfaction);
        }
    
        public String[] getData() {
            return new String[] {"Employee : " + employee.getJobSuccess(),"Manager: " + 
                manager.getSatisfaction()};
        }
    }
    

    Luego, necesitamos definir un CompositeEntity clase. Esta clase es en sí misma un objeto de grano grueso y puede hacer referencia a otro:

    public class CompositeEntity {
        private CoarseGrainedObject cgo = new CoarseGrainedObject();
    
        public void setData(String jobSuccess, String satisfaction) {
            cgo.setData(jobSuccess, satisfaction);
        }
    
        public String[] getData() {
            return cgo.getData();
        }
    }
    

    Con eso en su lugar, solo necesitamos un Client usar el CompositeEntity:

    public class Client {
        private CompositeEntity compositeEntity = new CompositeEntity();
    
        public void print() {
            for (int i = 0; i < compositeEntity.getData().length; i++) {
                System.out.println(compositeEntity.getData()[i]);
            }
        }
    
        public void setData(String jobSuccess, String satisfaction) {
            compositeEntity.setData(jobSuccess, satisfaction);
        }
    }
    

    Y para ilustrar el punto de este patrón:

    public class Main {
        public static void main(String[] args) {
            Client client = new Client();
            client.setData("Successful", "Satisfied");
            client.print();
            client.setData("Failed", "Unsatisfied");
            client.print();
        }
    }
    

    Ejecutar este fragmento de código producirá:

    Employee : Successful
    Manager: Satisfied
    Employee : Failed
    Manager: Unsatisfied
    

    Patrón de objeto de acceso a datos

    El patrón de objeto de acceso a datos, más a menudo abreviado a DAO, es un patrón en el que los objetos se dedican a la comunicación con la capa de datos.

    Estos objetos a menudo crean instancias de «SessionFactories» para este propósito y manejan toda la lógica detrás de la comunicación con la base de datos.

    La práctica estándar es crear una interfaz DAO, seguida de una clase concreta que implemente la interfaz y todos los métodos definidos en ella.

    Implementación

    Siguiendo la práctica estándar, definamos nuestra interfaz DAO:

    Te puede interesar:Interfaz iterable de Java: Iterator, ListIterator y Spliterator
    public interface EmployeeDAO {
        public List<Employee> getAllEmployees();
        public Employee getEmployeeById(int id);
        public void addEmployee(Employee e);
        public void updateEmployee(Employee e);
        public void deleteEmployee(Employee e);
    }
    

    Y nuestra clase de implementación concreta junto con ella:

    public class EmployeeDAOImpl implements EmployeeDAO {
        List<Employee> employeeList;
    
        public EmployeeDAOImpl() {
            employeeList = new ArrayList<Employee>();
            Employee david = new Employee(5, "David");
            Employee scott = new Employee(7, "Scott");
            Employee jessica = new Employee(12, "Jessica");
            Employee rebecca = new Employee(16, "Rebecca");
            employeeList.add(david);
            employeeList.add(scott);
            employeeList.add(jessica);
            employeeList.add(rebecca);
        }
    
        @Override
        public List<Employee> getAllEmployees() {
            return employeeList;
        }
        @Override
        public Employee getEmployeeById(int id) {
            return employeeList.get(id);
        }
        @Override
        public void addEmployee(Employee e) {
            employeeList.add(e);
            System.out.println("Successfully added " + e.getName());
        }
        @Override
        public void updateEmployee(Employee e) {
            employeeList.get(e.getEmployeeId()).setEmployeeName(e.getName());
            System.out.println("Successfully update name of employee with id: " + e.getEmployeeId());
        }
        @Override
        public void deleteEmployee(Employee e) {
            employeeList.remove(e.getEmployeeId());
            System.out.println("Successfully removed employee: " + e.getName() + "with the ID: " + e.getEmployeeId());
        }
    }
    

    Usaremos estas dos clases para agregar, recuperar, actualizar o eliminar usuarios de nuestra base de datos:

    public class Employee {
        private int employeeId;
        private String name;
    
        public Employee(int id, String name) {
            this.employeeId = id;
            this.name = name;
        }
    
        public int getEmployeeId() {
            return employeeId;
        }
        public void setEmployeeId(int id) {
            this.employeeId = id;
        }
        public String getName() {
            return name;
        }
        public void setEmployeeName(String name) {
            this.name = name;
        }
    }
    

    Y para ilustrar el punto de este patrón:

    public class Main {
        public static void main(String[] args) {
            EmployeeDAO employeeDao = new EmployeeDAOImpl();
    
            for(Employee employee : employeeDao.getAllEmployees()) {
                System.out.println("Employee info: |Name: " + employee.getName() + ", ID: " + employee.getEmployeeId() + "|");
            }
        }
    }
    

    Ejecutar este fragmento de código producirá:

    Employee info: |Name: David, ID: 5|
    Employee info: |Name: Scott, ID: 7|
    Employee info: |Name: Jessica, ID: 12|
    Employee info: |Name: Rebecca, ID: 16|
    

    Patrón de controlador frontal

    Al enviar una solicitud, el controlador frontal es el primer controlador al que llega. En base a la solicitud, decide qué controlador es el más adecuado para manejarlo, luego de lo cual pasa la solicitud al controlador elegido.

    El controlador frontal se usa con mayor frecuencia en aplicaciones web en forma de servlet de despachador.

    Implementación

    Para esta implementación, definiremos dos vistas simples, una FrontController y un Dispatcher:

    public class MainView {
        public void showView() {
            System.out.println("Showing main view.");
        }
    }
    
    public class EmployeeView {
        public void showView() {
            System.out.println("Showing Employee view.");
        }
    }
    

    Puede surgir una solicitud para cualquiera de estos dos en cualquier momento. Usamos el Dispatcher para atender la solicitud, apuntando a la vista correcta, después de la FrontController procesó la solicitud inicialmente:

    public class Dispatcher {
        private MainView mainView;
        private EmployeeView employeeView;
    
        public Dispatcher() {
            mainView = new MainView();
            employeeView = new EmployeeView();
        }
    
        public void dispatch(String request) {
            if(request.equalsIgnoreCase("EMPLOYEE")) {
                employeeView.showView();
            } else {
                mainView.showView();
            }
        }
    }
    
    public class FrontController {
        private Dispatcher dispatcher;
    
        public FrontController() {
            dispatcher = new Dispatcher();
        }
    
        private boolean isAuthenticUser() {
            System.out.println("User has successfully authenticated.");
            return true;
        }
    
        private void trackRequest(String request) {
            System.out.println("Request: " + request);
        }
    
        public void dispatchRequest(String request) {
            trackRequest(request);
    
            if(isAuthenticUser()) {
                dispatcher.dispatch(request);
            }
        }
    }
    

    Y para ilustrar el punto del patrón:

    public class Main {
        public static void main(String[] args) {
            FrontController frontController = new FrontController();
            frontController.dispatchRequest("MAIN");
            frontController.dispatchRequest("EMPLOYEE");
        }
    }
    

    Ejecutar este fragmento de código producirá:

    Request: MAIN
    User has successfully authenticated.
    Showing main view.
    Request: EMPLOYEE
    User has successfully authenticated.
    Showing Employee view.
    

    Patrón de filtro de interceptación

    Los filtros se utilizan incluso antes de que la solicitud se pase a los controladores adecuados para su procesamiento. Estos filtros pueden existir en forma de cadena de filtros e incluir varios filtros, o simplemente existir como un solo filtro.

    Sin embargo, ejecutan verificaciones de autorización, autenticación, navegadores compatibles, si la ruta de solicitud viola alguna restricción y restricción, etc.

    Te puede interesar:Métodos de objetos de Java: hashCode ()
    Implementación

    Haremos una cadena de filtros simple con un par de filtros para interceptar la solicitud después de alcanzar el objetivo.

    Comencemos definiendo una interfaz para el Filter sí mismo:

    public interface Filter {
        public void execute(String request);
    }
    

    Y un par de implementaciones concretas:

    public class AuthenticationFilter implements Filter {
        @Override
        public void execute(String request) {
            System.out.println("Authentication request: " + request);
        }
    }
    
    public class DebuggingFilter implements Filter {
        @Override
        public void execute(String request) {
            System.out.println("Logging request: " + request);
        }
    }
    

    Y finalmente, el Target de la solicitud:

    public class Target {
        public void execute(String request) {
            System.out.println("Executing request: " + request);
        }
    }
    

    Al definir un FilterChain, podemos agregar varios filtros para interceptar una solicitud. Definamos uno para nuestros dos filtros:

    public class FilterChain {
        private List<Filter> filters = new ArrayList<>();
        private Target target;
    
        public void addFilter(Filter filter) {
            filters.add(filter);
        }
    
        public void execute(String request) {
            for (Filter filter : filters) {
                filter.execute(request);
            }
            target.execute(request);
        }
    
        public void setTarget(Target target) {
            this.target = target;
        }
    }
    

    Ahora necesitamos una clase de gerente para ayudar a administrar esto FilterChain:

    public class FilterManager {
        FilterChain filterChain;
    
        public FilterManager(Target target) {
            filterChain = new FilterChain();
            filterChain.setTarget(target);
        }
    
        public void addFilter(Filter filter) {
            filterChain.addFilter(filter);
        }
    
        public void filterRequest(String request) {
            filterChain.execute(request);
        }
    }
    

    Y finalmente, el Client usará el FilterManager para enviar una solicitud a la aplicación:

    public class Client {
        FilterManager filterManager;
    
        public void setFilterManager(FilterManager filterManager) {
            this.filterManager = filterManager;
        }
    
        public void sendRequest(String request) {
            filterManager.filterRequest(request);
        }
    }
    

    Ahora para ilustrar el punto de este patrón:

    public class Main {
        public static void main(String[] args) {
            FilterManager filterManager = new FilterManager(new Target());
            filterManager.addFilter(new AuthenticationFilter());
            filterManager.addFilter(new DebuggingFilter());
    
            Client client = new Client();
            client.setFilterManager(filterManager);
            client.sendRequest("Index");
        }
    }
    

    Ejecutar este fragmento de código producirá:

    Authentication request: Index
    Logging request: Index
    Executing request: Index
    

    La solicitud ha pasado por ambos filtros desde el FilterChain, antes de ser enviado a la Target.

    Patrón de localizador de servicios

    Un patrón que se ve a menudo en las aplicaciones web, el patrón de localizador de servicios se utiliza para desacoplar los consumidores de servicios y las clases concretas como las implementaciones de DAO.

    El patrón busca el servicio adecuado, lo guarda en el almacenamiento en caché para reducir el número de solicitudes y por lo tanto la tensión en el servidor y proporciona a la aplicación sus instancias.

    Te puede interesar:Control de flujo de Java: la declaración del cambio
    Implementación

    Comencemos esta implementación definiendo un común Service interfaz:

    public interface Service {
        public String getServiceName();
        public void execute();
    }
    

    Un par de clases concretas implementarán esta interfaz:

    public class EmployeeService implements Service {
        @Override
        public String getServiceName() {
            return "Employee Service";
        }
    
        @Override
        public void execute() {
            System.out.println("Executing Employee Service...");
        }
    }
    
    public class CustomerService implements Service {
        @Override
        public String getServiceName() {
            return "Customer Service";
        }
    
        @Override
        public void execute() {
            System.out.println("Executing Customer Service...");
        }
    }
    

    Según el patrón, al buscar estos servicios, deberíamos almacenarlos en caché para reducir la tensión del servidor:

    public class Cache {
        private List<Service> services;
    
        public Cache() {
            services = new ArrayList<Service>();
        }
    
        public Service getService(String serviceName) {
            for(Service service : services) {
                if(service.getServiceName().equalsIgnoreCase(serviceName)) {
                    System.out.println("Returning cached " + serviceName);
                    return service;
                }
            }
            return null;
        }
    
        public void addService(Service newService) {
            boolean exists = false;
    
            for(Service service : services){
                if(service.getServiceName().equalsIgnoreCase(newService.getServiceName())) {
                    exists = true;
                }
            }
            if(!exists) {
                services.add(newService);
            }
        }
    }
    

    También necesitamos una clase para buscar e instanciar nuestros servicios:

    public class InitialContext {
        public Object lookup(String jndiName) {
            if(jndiName.equalsIgnoreCase("EmployeeService")) {
                System.out.println("Looking up and initializing Employee Service...");
                return new EmployeeService();
            } else if(jndiName.equalsIgnoreCase("CustomerService")) {
                System.out.println("Looking up and initializing Customer Service...");
                return new CustomerService();
            }
            return null;
        }
    }
    

    Y finalmente, podemos definir un Locator clase para exponer al cliente, que utiliza el InitialContext clase para buscar servicios, y el Cache class para almacenarlos en caché para su uso posterior.

    public class Locator {
        private static Cache cache;
    
        static {
            cache = new Cache();
        }
    
        public static Service getService(String jndiName) {
            Service service = cache.getService(jndiName);
    
            if(service != null) {
                return service;
            }
    
            InitialContext context = new InitialContext();
            Service service1 = (Service)context.lookup(jndiName);
            cache.addService(service1);
            return service1;
        }
    }
    

    Y para ilustrar el punto de este patrón:

    public class Main {
        public static void main(String[] args) {
            Service service = Locator.getService("EmployeeService");
            service.execute();
            service = Locator.getService("CustomerService");
            service.execute();
        }
    }
    

    Ejecutar este fragmento de código producirá:

    Looking up and initializing Employee Service...
    Executing Employee Service...
    Looking up and initializing Customer Service...
    Executing Customer Service...
    

    Patrón de objeto de transferencia

    Este patrón se utiliza para transferir objetos con muchos campos y parámetros de una sola vez. El patrón Transfer Object emplea nuevos objetos, que se utilizan solo con fines de transferencia, y que generalmente se pasan al DAO.

    Estos objetos son POJO serializables. Tienen campos, sus respectivos getters y setters, y ninguna otra lógica.

    Implementación

    Un objeto puede verse así:

    public class EmployeeVO {
        private int employeeId;
        private String name;
    
        public EmployeeVO(int employeeId, String name) {
            this.employeeId = employeeId;
            this.name = name;
        }
    
        public int getEmployeeId() {
            return employeeId;
        }
    
        public void setEmployeeId(int id) {
            this.employeeId = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    Tenga en cuenta que el objeto contiene solo unos pocos campos por brevedad.

    Un ejemplo de un objeto nuevo, que se emplea solo con fines de transferencia:

    Te puede interesar:Control de flujo de Java: declaraciones while y do-while
    public class EmployeeBO {
        List<EmployeeVO> employees;
    
        public EmployeeBO() {
            employees = new ArrayList<>();
            EmployeeVO david = new EmployeeVO(1, "David");
            EmployeeVO scott = new EmployeeVO(2, "Scott");
            EmployeeVO jessica = new EmployeeVO(3, "Jessica");
            employees.add(david);
            employees.add(scott);
            employees.add(jessica);
        }
    
        public void deleteEmployee(EmployeeVO employee) {
            employees.remove(employee.getEmployeeId());
            System.out.println("Employee with ID: " + employee.getEmployeeId() + " was successfully deleted.");
        }
    
        public List<EmployeeVO> getAllEmployees() {
            return employees;
        }
    
        public EmployeeVO getEmployee(int id) {
            return employees.get(id);
        }
    
        public void updateEmployee(EmployeeVO employee) {
            employees.get(employee.getEmployeeId()).setName(employee.getName());
            System.out.println("Employee with ID: " + employee.getEmployeeId() + " successfully updated.");
        }
    }
    

    Y para ilustrar el punto del patrón:

    public class Main {
        public static void main(String[] args) {
            EmployeeBO employeeBo = new EmployeeBO();
    
            for(EmployeeVO employee : employeeBo.getAllEmployees()) {
                System.out.println("Employee: |" + employee.getName() + ", ID: " + employee.getEmployeeId() + "|");
            }
    
            EmployeeVO employee = employeeBo.getAllEmployees().get(0);
            employee.setName("Andrew");
            employeeBo.updateEmployee(employee);
    
            employee = employeeBo.getEmployee(0);
            System.out.println("Employee: |" + employee.getName() + ", ID: " + employee.getEmployeeId() + "|");
    
        }
    }
    

    Ejecutar este fragmento de código producirá:

    Employee: |David, ID: 1|
    Employee: |Scott, ID: 2|
    Employee: |Jessica, ID: 3|
    Employee with ID: 1 successfully updated.
    Employee: |Andrew, ID: 1|
    

    Conclusión

    Con esto, todos los patrones de diseño J2EE en Java están completamente cubiertos, con ejemplos de trabajo.

    Con esto concluye nuestra breve serie de artículos sobre patrones de diseño de Java. Si encontró este informativo y se perdió alguno de los anteriores, no dude en consultarlos también:

    • Patrones de diseño creacional en Java
    • Patrones de diseño estructural en Java
    • Patrones de diseño de comportamiento en Java
    Rate this post

    Etiquetas: