Skip to content

Patrones Java Fundamentales: Observer, Command, Composite

Muchos de nosotros hemos utilizado un sinnúmero de frameworks web interesantes como Struts, JSF etc.. muchos de los cuales han sabido ubicarse en las tecnologías más utilizadas debido al uso de patrones de diseño.

En esta oportunidad se explicarán tres Patrones que han sido fundamentales en el diseño y utilización de los frameworks como Struts y JSF y que es importante que los entendamos, estos son:

  • Composite Pattern
  • Observer Pattern
  • Command Pattern

Composite Pattern
La creación de interfaces de usuario web muchas veces involucra manejar bloques similares o repetitivos como logos, cabeceras, menus, en muchas de las pantallas una manera de solucionarlo es utilizar el patrón Composite. Este patrón ayuda a subdivir nuestras interfaces de usuario en pequeñas piezas y crear nuevas piezas componiendo nuevamente estas piezas juntas pero en diferentes maneras lo cual garantiza menos código repetivo y más reutilización, ejemplo de su utilización podemos encontrarlos en las tecnologías afines a Struts y JSF como Tiles y Facelets respectivamente.

Command Pattern
Este patrón enfatiza el uso de objetos como acciones ejecutables, es decir un objeto encapsula una simple accion (método) que implementa de una interface el cual es invocado por un objeto Controlador, ejemplo de su utilización podemos encontrarlo en Struts.

Observer Pattern
Este patrón es común utilizado en la programación de interfaces gráficas (GUI) que se base en eventos y manejadores de eventos, también se lo conoce con el nombre de modelo Publicación/Subscripción.
Una ventaja de este patrón es que un simple objeto puede manejar un sinnúmero de eventos para una simple interacción del usuario en una pantalla, un ejemplo de su utilización lo encontramos en JSF.

CRUD Java Basico sin BDD

En este post se realizará un ejemplo básico de manejo de operaciones CRUD pero sin base de datos todo manejado a traves de collections y bloques estáticos. La aplicación se utilizará es para manejar la compra de vehículos.

El proyecto definirá la siguiente arquitectura:

  • Tres capas lógicas representadas en tres packages basados en un modelo MVC
  • Una clase llamada MemoriaBDD.java que representará nuestra BDD

Cada capa lógica o package tiene su ámbito basado en la siguiente especificación de patrón:

  • El package modelo representará los objetos persistentes o tablas de la BDD
  • El package controlador contendrá toda la lógica de negocio, en esta caso las operaciones CRUD y demás.
  • El package vista contendrá las interfaces o formularios que el usuario final llenará o interactuará, en este caso es una clase llamada Principal que recepta los datos desde consola.

Comenzaré explicando la clase MemoriaBDD.java que se encuentra en el package modelo y que representa nuestra Base de Datos. A continuación se muestra la misma.

MemoriaBdd.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.matoosfe.controlador;

import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;

import com.matoosfe.modelo.Buseta;
import com.matoosfe.modelo.Cliente;
import com.matoosfe.modelo.Factura;
import com.matoosfe.modelo.Moto;
import com.matoosfe.modelo.Trailer;
import com.matoosfe.modelo.Vehiculo;


/**
 * Clase que representa la badse de Datos
 * @author martosfre
 * @see www.matoosfe.com
 */

public class MemoriaBDD {
    public static Set<Vehiculo> vehiculos;
    public static Set<Cliente> clientes;
    public static Set<Factura> facturas;
   
    static{
       
        //Inicilializar Facturas
        facturas = new HashSet<Factura>();
       
        //Inicializar Vehiculos
        vehiculos = new HashSet<Vehiculo>();
        Moto motoUno = new Moto("azul","PBB-987","QMC","2007", new BigDecimal(1567.45d));
        Moto motoDos = new Moto("verde","PAL-747","HONDA","2010", new BigDecimal(1000.45d));
        vehiculos.add(motoUno);
        vehiculos.add(motoDos);
       
        Trailer trailerUno = new Trailer("rojo","UBJ-098","MERCEDES BENZ","2005", new BigDecimal(144000));
        Trailer trailerDos = new Trailer("azul","XJT-233","MERCEDES BENZ","2004", new BigDecimal(124000));
        vehiculos.add(trailerUno);
        vehiculos.add(trailerDos);
       
        Buseta busUno = new Buseta("amarilla","GYT-9889","HYUNDAI","2002", new BigDecimal(75000));
        Buseta busDos = new Buseta("amarilla","PKT-956","CHEVROLET","1999", new BigDecimal(55000));
        vehiculos.add(busUno);
        vehiculos.add(busDos);
       
        //Inicializar clientes
        clientes = new HashSet<Cliente>();
        Cliente clienteUno = new Cliente("Vazquez", "Juan", "1111111189", "Las Casas", "098890988");
        Cliente clienteDos = new Cliente("Sanchez", "Luis", "1234567890", "El Dorado", "097090988");
        Cliente clienteTres = new Cliente("Prado", "Maria Fernanda", "222222220", "Carapungo", "2345659");
        clientes.add(clienteUno);
        clientes.add(clienteDos);
        clientes.add(clienteTres);
       
    }      
}

En esta clase se hizo uso de bloque estático que permite inicializar objetos o ejecutar procesos automáticos. Para este caso permitirá inicializar los objetos o “tablas” de nuestro BDD pero en memoria es decir luego de que la aplicación se cierre todas las operaciones o los nuevos registros (objetos colocados en cada collection adicional a los que ya se inicilizan) se perderán.

Ahora voy a explicar la lógica de negocio la cual será implementada en el package controlador en la clases AdminFactura.java, AdminVehiculo.java, AdminCliente.javalas cuales contendrán todas los métodos de negocio. A continuación se muestra las clases en cuestión.

AdminCliente.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.matoosfe.controlador;

import com.matoosfe.modelo.Cliente;

/**
 * Esta clase permitirá manejar toda la lógica de negocio relacionada con clientes
 * @author martosfre
 * @see www.matoosfe.com
 */

public class AdminCliente {
   
   
    /**
     * Método para guardar un cliente
     * @param cliente
     * @return
     */

    public static String guardarCliente(Cliente cliente){
        MemoriaBDD.clientes.add(cliente);
        return "Registro Guardado Satisfactoriamente";
    }

    /**
     * Método para buscar un cliente por la cédula de identidad
     * @param cedIn
     * @return
     */

    public static Cliente buscarClienteByCedula(String cedIn) {
        Cliente clienteDev = null;
        for(Cliente cli: MemoriaBDD.clientes){
            if(cli.getCedula().equals(cedIn)){
                clienteDev = cli;
                break;
            }
        }
        return clienteDev;
    }
   
    /**
     * Método para actualizar un cliente
     * @param cedIn
     * @param clienteAct
     * @return
     */

    public static String actualizarCliente(String cedIn, Cliente clienteAct){
       
        guardarCliente(clienteAct);
        return "Registro actualizado Satisfactoriamente";
    }

    /**
     * Método para eliminar un cliente
     * @param cedIn
     * @return
     */

    public static String eliminarCliente(String cedIn) {
        Cliente clienteOri = buscarClienteByCedula(cedIn);
        boolean confEli = MemoriaBDD.clientes.remove(clienteOri);
        if(confEli){
            return "Registro eliminado Satisfactoriamente";
        }else{
            return "No se pudo eliminar el cliente";
        }
    }

}

AdminVehiculo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.matoosfe.controlador;


import com.matoosfe.modelo.Vehiculo;


/**
 *
 * Esta clase permite manipular todas las operaciones relacionadas con Vehiculo
 * @author martosfre
 * @see www.matoosfe.com
 * Aug 18, 2010
 */

public class AdminVehiculo {

    /**
     * Método para guardar un vehículo
     * @param vehiculoGen
     * @return
     */

    public String guardarVehiculo(Vehiculo vehiculoGen){
        MemoriaBDD.vehiculos.add(vehiculoGen);
        return "Registro Guardado Satisfactoriamente";
    }


    /**
     * Método para buscar un vehículo de acuerdo a la placa y tipo
     * @param placa
     * @param tipo
     * @return
     */

    public static Vehiculo buscarVehiculoPorPlaca(String placa) {
        Vehiculo vehDev = null;

        for (Vehiculo veh : MemoriaBDD.vehiculos) {
            if(veh.getPlaca().equals(placa)){
                vehDev = veh;
                break;
            }
        }

        return vehDev;
    }  
}

AdminFactura.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.matoosfe.controlador;

import java.util.ArrayList;
import java.util.List;

import com.matoosfe.modelo.Factura;

/**
 *
 * Esta clase permitirá manejar todas las operaciones relacionadas con Factura
 * @author martosfre
 * @see www.matoosfe.com
 * Aug 18, 2010
 */

public class AdminFactura {
   
    /**
     * Método para guardar una factura
     * @param factura
     * @return
     */

    public static String guardarFactura(Factura factura){
        MemoriaBDD.facturas.add(factura);
        return "Factura registrada Exitosamente";
    }
   
    /**
     * Métodos para buscar facturas por cliente de acuerdo
     * a la cédula
     * @param cedCliente
     * @return
     */

    public static List<Factura> buscarFacturasPorCliente(String cedCliente){
        List<Factura> facturasCliente = new ArrayList<Factura>();
        for (Factura factura : MemoriaBDD.facturas) {
            if(factura.getCliente().getCedula().equals(cedCliente)){
                facturasCliente.add(factura);
            }
        }
        return facturasCliente;
    }

}

En estas clases se puede apreciar que todas las operaciones CRUD están relacionadas directamente con la clase MemoriaBDD.java en sus atributos representados como collections que para este caso son las tablas de la base de datos.

Por último se explicará la clase Formulario.java que se encuentra en el package vista que representa el formulario que será manipulado por el usuario final.

Formulario.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package com.matoosfe.vista;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.matoosfe.controlador.AdminCliente;
import com.matoosfe.controlador.AdminFactura;
import com.matoosfe.controlador.AdminVehiculo;
import com.matoosfe.modelo.Cliente;
import com.matoosfe.modelo.DetalleFactura;
import com.matoosfe.modelo.Factura;
import com.matoosfe.modelo.Vehiculo;

/**
 *
 * Esta clase representa el formulario de ingreso de la información
 * para la gestión de la compra de los vehículos
 * @author martosfre
 * @see www.matoosfe.com
 * Aug 18, 2010
 */

public class Formulario {

    public void registrarFactura(){
        //Recibo los datos del vehiculo(s) que son los detalles, cliente
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.print("Ingrese el cliente que va a comprar:");
        try {
            String cedIn = br.readLine();
            Cliente cliente = AdminCliente.buscarClienteByCedula(cedIn);
            Factura facturaCompra = new Factura();
            List<DetalleFactura> detallesFactura = new ArrayList<DetalleFactura>();
            BigDecimal subtotal = new BigDecimal(0.0d);
            BigDecimal total = new BigDecimal(0.0d);
           
            System.out.println("\n*************DATOS VEHICULO************");
            String placaVeh = "1";

            //Registro los vehículos que se vayan a comprar
            while(!placaVeh.equals("0")){
                System.out.print("Ingrese vehículo codigo:");
                placaVeh = br.readLine();
               
                //Buscamos al vehículo para registrarlo
                Vehiculo vehTmp = AdminVehiculo.buscarVehiculoPorPlaca(placaVeh);

                //Verificamos que si se encontró el vehiculo guardamos el detalle
                if(vehTmp != null){
                    //Ingresando cada detalle
                    DetalleFactura detalleTmp = new DetalleFactura();
                    detalleTmp.setCantidad(1);
                    detalleTmp.setFactura(facturaCompra);
                    detalleTmp.setVehiculo(vehTmp);
                    detalleTmp.setPrecio(vehTmp.getPrecio());
                    detallesFactura.add(detalleTmp);
                    subtotal = subtotal.add(vehTmp.getPrecio());

                }
            }
           
            //Añado detalles y calculo el IVA
            facturaCompra.setDetalles(detallesFactura);
            total = subtotal.add(subtotal.multiply(new BigDecimal(0.12d)));
           
            //Registro la factura siempre que haya minimo un detalle
            if(facturaCompra.getDetalles().size() > 1){
                facturaCompra.setCliente(cliente);
                facturaCompra.setFechaCompra(new Date());
                facturaCompra.setNumeroFactura(String.valueOf(Math.random()));
                facturaCompra.setDetalles(detallesFactura);
                facturaCompra.setSubtotal(subtotal);
                facturaCompra.setTotal(total);
            }
           
            //Guardamos la factura
            AdminFactura.guardarFactura(facturaCompra);
           
            //Imprimir Registro Compra
            System.out.println("\n*************DATOS COMPRA************");
            System.out.println("Cliente:" +  cliente.getNombres() + " " +  cliente.getApellidos());
            for (Factura fac : AdminFactura.buscarFacturasPorCliente(cliente.getCedula())) {
                System.out.println("Factura:" +  fac.getNumeroFactura());
                System.out.println("Total Factura:" + fac.getTotal());
                //Imprimir Vehiculos comprados
                for (DetalleFactura det : fac.getDetalles()) {
                    System.out.println("Vehiculo Marca:" +  det.getVehiculo().getMarca());
                    System.out.println("Vehiculo Modelo:" +  det.getVehiculo().getModelo());
                    System.out.println("Vehiculo Placa:" +  det.getVehiculo().getPlaca());
                }
            }
           
           
        } catch (NumberFormatException e) {
            System.out.println("Error Formato");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("Error Lectura");
            e.printStackTrace();
        }
        //Aqui ya tengo el cliente y todos los vehiculos que quiere comprar

    }

    public static void main(String[] args) {
        Formulario pr = new Formulario();
        pr.registrarFactura();
    }
}

En esta clase se buscará al cliente que va a comprar el vehículo, luego se registrarán todos los vehículos que se comprarán como un item del detalle, asociando al final todos los detalles junto con el cliente a la factura que se guardará o procesará.

Soporte java.io en GWT – Google App Engine

Si se está desarrollando aplicaciones con GWT y Google App Engine se debe considerar que las clases pertenecientes al paquete java.io no son soportados por el Servidor por lo cual se debe hacer uso de la librería appengine-java-io la cual una vez descargada debe utilizar en vez de la java.io.

Cabe destacar que esto es necesario cuando se utiliza el servidor Google App Engine ya que si no se lo hace y se deploya la aplicación sobre cualquier otro servidor no se tendrá problemas.

Queries Hibernate

Para manipular los queries en Hibernate se tiene tres mecanismos:

  • Queries Nativos
  • HQL a través de la clase Query
  • Queries Programáticos a través del Query Criteria API

Para ejemplificar su utilización se realizará una consulta simple y una consulta compleja con cada mecanismo sobre un esquema relacional de dos tablas. Las consultas junto con el esquema que se utilizarán se muestran a continuación:

Consultas SQL

1
2
3
4
5
6
7
8
SELECT *
FROM Curso c WHERE c.numeroCreditosCur > 5
AND c.numeroCreditosCur < 8;

SELECT *
FROM Curso c, Materia m
WHERE  c.Materia_idMateria = m.idMateria AND
m.nombreMat LIKE '%java%';

Ahora estos queries los migraremos al mundo objetual en hibernate a través de los mecanismos mencionados anteriormente. Comencemos..

Queries Nativos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
     * Método para devolver los cursos que tengan un número de créditos
     * entre un rango dado por los parámetros ingresados mediante SQL Nativo
     * @param numMayor
     * @param numMen
     * @return
     */

    public static List<Curso> devolverCursosPorRangoSQLNativo(int numMayor, int numMen){
        List<Curso> cursos = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            session.beginTransaction();
            SQLQuery query = session.createSQLQuery("SELECT {c.*} FROM Curso c " +
                    " WHERE c.numeroCreditosCur > " + numMayor +
                    " AND c.numeroCreditosCur < " + numMen);
            query.addEntity("c", Curso.class);
            cursos = query.list();
            session.getTransaction().commit();
        } catch (HibernateException e) {
            e.printStackTrace();
            System.out.println("Error Criteria:" + e.getMessage());
        }

        return cursos;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
     * Método que devuelve los cursos deacuerdo al nombre de una materia dada
     * utilizando SQL Nativo
     * @param nombre
     * @return
     */

    public static List<Curso> devolverCursosCriteriaPorNombreMateriaSQLNativo(String nombre){
        List<Curso> cursos = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            session.beginTransaction();
            SQLQuery query = session.createSQLQuery("SELECT {c.*} FROM Curso c, Materia m " +
                    " WHERE c.Materia_idMateria = m.idMateria AND " +
                    " m.nombreMat like '%" + nombre + "%'");
            query.addEntity("c", Curso.class);
            //Paginación
            cursos = query.list();
            session.getTransaction().commit();
        } catch (HibernateException e) {
            e.printStackTrace();
            System.out.println("Error Criteria:" + e.getMessage());
        }

        return cursos;
    }

HQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
     * Método para devolver los cursos que tengan un número de créditos
     * entre un rango dado por los parámetros ingresados mediante HQL
     * @param numMayor
     * @param numMen
     * @return
     */

    public static List<Curso> devolverCursosPorRangoHQL(int numMayor, int numMen){
        List<Curso> cursos = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            session.beginTransaction();
            Query query = session.createQuery("from Curso c where c.numeroCreditosCur >:numMay " +
                    " and c.numeroCreditosCur <:numMen ");
            query.setParameter("numMay", numMayor);
            query.setParameter("numMen", numMen);
            cursos = query.list();
            session.getTransaction().commit();
        } catch (HibernateException e) {
            e.printStackTrace();
            System.out.println("Error Criteria:" + e.getMessage());
        }

        return cursos;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
     * Método que devuelve los cursos deacuerdo al nombre de una materia dada
     * utilizando HQL
     * @param nombre
     * @return
     */

    public static List<Curso> devolverCursosCriteriaPorNombreMateriaHQL(String nombre){
        List<Curso> cursos = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            session.beginTransaction();
            Query query = session.createQuery("Select c from Curso c left join fetch c.materia m " +
                    " where m.nombreMat like:nombre");
            query.setParameter("nombre", "%" + nombre + "%");  
            //Paginación
            query.setFirstResult(0);
            query.setMaxResults(20);
            cursos = query.list();
            session.getTransaction().commit();
        } catch (HibernateException e) {
            e.printStackTrace();
            System.out.println("Error Criteria:" + e.getMessage());
        }

        return cursos;
    }

Query Criteria API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    /**
     * Método para devolver los cursos que tengan un número de créditos
     * entre un rango dado por los parámetros ingresados.
     * @param numMayor
     * @param numMen
     * @return
     */

    public static List<Curso> devolverCursosPorRango(int numMayor, int numMen){
        List<Curso> cursos = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            session.beginTransaction();
            Criteria criteria = session.createCriteria(Curso.class);
            criteria.add(Restrictions.gt("numeroCreditosCur", numMayor));
            criteria.add(Restrictions.lt("numeroCreditosCur", numMen));
            cursos = criteria.list();
            session.getTransaction().commit();
        } catch (HibernateException e) {
            e.printStackTrace();
            System.out.println("Error Criteria:" + e.getMessage());
        }

        return cursos;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    /**
     * Método que devuelve los cursos de una materia dada
     * @param nombre
     * @return
     */

    public static List<Curso> devolverCursosCriteriaPorNombreMateria(String nombre){
        List<Curso> cursos = null;
        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            session.beginTransaction();
            Criteria criteria = session.createCriteria(Curso.class);
            Criteria criteriaMat = criteria.createCriteria("materia");
            criteriaMat.add(Restrictions.like("nombreMat", "%" + nombre + "%"));
            //Paginación
            criteria.setFirstResult(0);
            criteria.setMaxResults(20);
            cursos = criteria.list();
            session.getTransaction().commit();
        } catch (HibernateException e) {
            e.printStackTrace();
            System.out.println("Error Criteria:" + e.getMessage());
        }

        return cursos;
    }

Estos representan las tres formas de recuperar los datos en Hibernate, cabe destacar que en cada forma existen peculiaridades que se deberán abordar para ser más específicos en los queries realizados.