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:

    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
    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *