Patrones de diseño Java J2EE

P

 

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:

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.

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:

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.

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.

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:

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

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad