¡Dos entradas en dos días! ¿Qué puedo decir?...
Creo que este fin de semana estoy inspirado, y eso es bueno :D
En fin, vamos a pasar a otro temilla rápido que nos ocupará una nueva entrada en este blog... y es el trabajar con DELTA en java.
¡Vamos allá!
Para empezar... ¿qué es un DELTA?
Concepto de DELTA
Pues no me refiero a un ala delta (ba dum tss), si no a un método de comparación de datos que se basa en encontrar los cambios entre dos versiones del mismo conjunto de datos.
¿Nani?
Con un ejemplo se ve mejor (aunque ya sabéis que mis ejemplos suelen ser malísimos, pero me esfuerzo).
Supongamos durante lo que dura esta entrada, que somos los gerentes de una empresa, y queremos ver los cambios que se generan en nuestra plantilla mes a mes (nuestra empresa es pequeña y no tiene sentido mirar los cambios a diario).
Podríamos tener, por ejemplo, la siguiente plantilla
Mes de Enero:
- Empleado 1
- Nombre: David
- Puesto: Gerente
- Horas semanales: 100
- Empleado 2
- Nombre: Oscar
- Puesto: Encargado de la limpieza
- Horas semanales: 40
- Empleado 3
- Nombre: Solé
- Puesto: Programador Junior
- Horas semanales: 20
- Empleado 5
- Nombre: González
- Puesto Programador Senior
- Horas semanales: 50
Mes de Febrero:
- Empleado 1
- Nombre: David
- Puesto: Gerente
- Horas semanales: 90
- Empleado 2
- Nombre: Oscar
- Puesto: Encargado de la limpieza
- Horas semanales: 40
- Empleado 5
- Nombre: González
- Puesto: Analista
- Horas semanales: 50
- Empleado 4
- Nombre: Solé Jr.
- Puesto: Programador Junior
- Horas Semanales: 20
Bien, de aquí vamos a sacar unos cuantos conceptos...
- Todos los empleados de la plantilla se consideran un conjunto de datos
- El conjunto de datos de enero lo llamaremos "antiguo"
- El conjunto de datos de febrero lo llamaremos "actual"
Si comparamos "antiguo" y "actual", observamos
- David ha reducido su horario (aunque no vamos a tenerlo en cuenta para el DELTA)
- González ha cambiado de puesto
- Solé ha dejado la empresa
- Solé Jr. se ha incorporado a la empresa
Estos cuatro cambios, sería el resultado de aplicar un DELTA a "actual" sobre "antiguo".
Realizando un DELTA en Java
Bien, si aplicamos todo esto a Java, es relativamente sencillo tener un mapa mental claro... cada empleado sería una instancia de una nueva clase Empleado, y simplemente bastaría con hacer un par de operaciones con los conjuntos de datos para obtener los objetos que se han mantenido, y los que han cambiado (y dentro de estos últimos, cuales se han añadido - insert -, cuales han cambiado - update - y cuales se han eliminado - delete -).
Separando los datos comunes
Esta parte es sencilla. Básicamente tomamos "antiguo", y lo recorremos de arriba a abajo. Por cada elemento de "antiguo", miramos si existe en "actual".
Si existe, es un dato común; si no, es un dato no común, y lo almacenamos a parte.
Para los datos no comunes, ya tenemos una parte hecha en el funcionamiento descrito en los párrafos anteriores; lo que nos queda por hacer ahora es hacer la inversa: recorrer "actual" y comparar con "actual", pero solo quedándonos con los datos no comunes (los comunes ya los tenemos localizados).
En este punto, la comparación la debemos hacer de un modo completo... es decir, buscamos cualquier tipo de cambio relevante (cambios en el número de empleado, en el nombre, o en el puesto, pero no en las horas).
Con esto, deberíamos tener tres subconjuntos de datos
- Datos que solo existen en "antiguo" (alpha)
- Datos que solo existen en "nuevo" (beta)
- Datos comunes
Tipología del cambio
Ahora, haremos una comparación similar a la anterior, pero con "alpha" y "beta", y esta vez solo compararemos los datos clave (número de empleado).
Tras esta comparativa, tendremos otros tres subconjuntos de datos
- Datos que solo existen en "alpha" (no existen en "beta", por tanto, se han borrado)
- Datos que solo existen en "beta" (no existen en "alpha", por tanto, se han añadido)
- Datos que existen en "alpha" y "beta" (existen en ambos, por tanto, se han modificado).
Todo este procedimiento lo podemos encapsular en una clase, pero previamente hay que establecer unos requerimientos.
Clase base
Necesitamos garantizar que la clase sobre la que hagamos el DELTA tenga un método que compare un objeto dado con la propia instancia, y que además, podamos indicar si queremos hacer la comparación de todos los datos, o únicamente de los datos clave.
Para ello, crearemos una clase abstracta llamada "Historify", en la que fijaremos nuestros requerimientos.
Get Raw
0001/**
0002 * Historify Class.
0003 *
0004 * Base Class to be used on Delta Class. It provides the basic methods to be
0005 * used on Delta.
0006 *
0007 * @author DoHITB
0008 *
0009 */
0010public abstract class Historify{
0011 /**
0012 * Object Id. It identifies each class instance
0013 */
0014 private Object id;
0015
0016 /**
0017 * Class Constructor.
0018 *
0019 * It checks that "id" cannot be null.
0020 * @param id: instance ID
0021 * @throws NullIdException if id attribute is null
0022 */
0023 public Historify(Object id) throws NullIdException{
0024 if(id == null)
0025 throw new NullIdException("Invalid ID provided");
0026
0027 this.id = id;
0028 }
0029
0030 /**
0031 * Equals method.
0032 *
0033 * It compares current instance against given object. Both objects must
0034 * have same class to be compared.
0035 *
0036 * @param o: object to compare against
0037 * @param strict: if true, full comparation will be done (hardEquals)
0038 * @return true if equals; false otherwise
0039 * @throws ClassDifferException if o class differs from current class.
0040 */
0041 public final boolean equals(Historify o, boolean strict)
0042 throws ClassDifferException {
0043 if(!o.getClass().equals(this.getClass()))
0044 throw new ClassDifferException(this, o);
0045
0046 if(strict)
0047 return this.hardEquals(o);
0048
0049 return this.id.equals(o.getId());
0050 }
0051
0052 /**
0053 * HardEquals method.
0054 *
0055 * It makes a full comparation of all attributes on the class.
0056 * @param o: object to compare against
0057 * @return true if equals; false otherwise
0058 */
0059 public abstract boolean hardEquals(Historify o);
0060
0061 /**
0062 * GetId method.
0063 *
0064 * Getter for "id" attribute
0065 * @return id
0066 */
0067 public Object getId() {
0068 return this.id;
0069 }
0070}
Con esta clase, podemos asegurar que todos las clases que hereden "Historify" tendrán los mecanismos que necesitamos.
Ahora, vamos a pasar a implementar el DELTA.
Clase DELTA
Para esta clase, vamos a usar lo que se conoce como tipo genérico. Es decir, sabemos que vamos a estar trabajando con una subclase de "Historify" (ya que esta clase es abstracta y no se puede instanciar), así que no podemos usar una clase en concreto... esto se soluciona, efectivamente, con un tipo genérico.
Get Raw
0001import java.util.ArrayList;
0003/**
0004 * Delta Class
0005 *
0006 * Helper class that is suitable to do delta comparation between two data array
0007 *
0008 * The data array must have same Class, either it would not be able to compare
0009 * Also, the Class must extend the Historify Class, as it have comparation
0010 * methods declared that are being used on this class:
0011 *
0012 * - equals: this is a final boolean method, so there is no need to implement
0013 * it on subclasses. It does a compare, but only having in mind the
0014 * ID's.
0015 *
0016 * - hardEquals: this method must be implemented on each subclass. It may
0017 * do a compare of all attributes in class.
0018 *
0019 * @author DoHITB
0020 *
0021 * @param : The Class is being used.
0022 */
0023public class Delta <T extends historify>{
0024 private T[] old;
0025 private T[] cur;
0026
0027 private ArrayList<T> ins;
0028 private ArrayList<T> upd;
0029 private ArrayList<T> del;
0030 private ArrayList<T> alpha;
0031 private ArrayList<T> aandb;
0032 private ArrayList<T> beta;
0033
0034 /**
0035 * Class constructor
0036 *
0037 * @param old: an array of <T> Class having the previous data
0038 * @param cur: an array of <T> Class having the current data
0039 */
0040 public Delta(T[] old, T[] cur) {
0041 this.old = old;
0042 this.cur = cur;
0043 }
0044
0045 /**
0046 * Main method for Delta Class.
0047 *
0048 * It distributes data, then do the Delta.
0049 * After this execution, "ins", "upd", and "del" will be fulfilled.
0050 *
0051 * @throws ClassDifferException if "old" and "cur" have different class.
0052 */
0053 public void check() throws ClassDifferException{
0054 //restart all the data
0055 init();
0056
0057 //first, distribute all the data.
0058 this.distribute();
0059
0060 //then, do the delta
0061 this.doDelta();
0062 }
0063
0064 /**
0065 * Distribute method.
0066 *
0067 * Based on "old" and "cur", it distributes which data are on both sides,
0068 * and which one is only on one of the data arrays
0069 *
0070 * @throws ClassDifferException if "old" and "cur" have different class.
0071 */
0072 private void distribute() throws ClassDifferException{
0073 //first, check old vs cur
0074 this.compare(this.old, this.cur, true, this.aandb, this.alpha, true);
0075
0076 //then, check for cur vs old
0077 this.compare(this.cur, this.old, false, null, this.beta, true);
0078 }
0079
0080 @SuppressWarnings("unchecked")
0081 /**
0082 * doDelta method.
0083 *
0084 * Based on the results of "distribute", it distributes which data are on
0085 * both sides, and which one is only on one of the data arrays
0086 *
0087 * @throws ClassDifferException if "old" and "cur" have different class.
0088 */
0089 private void doDelta() throws ClassDifferException{
0090 T[] alphaA = (T[]) new Historify[this.alpha.size()];
0091 T[] betaA = (T[]) new Historify[this.beta.size()];
0092
0093 alphaA = this.alpha.toArray(alphaA);
0094 betaA = this.beta.toArray(betaA);
0095
0096 //finally, check alpha vs beta, and beta vs alpha
0097 this.compare(alphaA, betaA, true, this.upd, this.del, false);
0098
0099 this.compare(betaA, alphaA, false, null, this.ins, false);
0100 }
0101
0102 /**
0103 * Compare method.
0104 *
0105 * It checks if given array exists on the given array. If "storeEquals" is
0106 * true, it will store all data existing on the given array. If "strict" is
0107 * true, it will do a full comparation of the object against the data array
0108 *
0109 * @param a: array to be checked object by object
0110 * @param b: array to be checked against
0111 * @param storeEquals: if true, positive results will be stored
0112 * @param equal: ArrayList on which positive results will be stored
0113 * @param differ: ArrayList on which negative results will be stored
0114 * @param strict: if true, a full comparation will be done.
0115 * @throws ClassDifferException if "old" and "cur" have different class.
0116 */
0117 private void compare(T[] a, T[] b, boolean storeEquals,
0118 ArrayList<T> equal, ArrayList<T> differ,
0119 boolean strict) throws ClassDifferException{
0120 for(T obj : a)
0121 if(this.equals(obj, b, strict))
0122 //the Object exist both in old and cur
0123 this.storeEquals(storeEquals, equal, obj);
0124 else
0125 //the Object only exist in old
0126 differ.add(obj);
0127 }
0128
0129 /**
0130 * Equals method.
0131 *
0132 * It checks if given object exists on the given array. If "strict" is true
0133 * it will do a full compare; else it will only compare both ID's.
0134 *
0135 * @param o: object to be checked against array
0136 * @param a: array to be checked against
0137 * @param strict: if true, it will do a full comparation
0138 * @return true if found, else otherwise
0139 * @throws ClassDifferException if "old" and "cur" have different class.
0140 */
0141 private boolean equals(T o, T[] a, boolean strict)
0142 throws ClassDifferException{
0143 for(T obj : a)
0144 if(obj.equals(o, strict))
0145 return true;
0146
0147 return false;
0148 }
0149
0150 /**
0151 * StoreEquals method.
0152 *
0153 * It checks if positive results may be stored, then store if necessary
0154 *
0155 * @param done: if true, results will be stored
0156 * @param target: ArrayList to be increased
0157 * @param item: Object to be stored
0158 */
0159 private void storeEquals(boolean done, ArrayList<T> target, T item) {
0160 if(done)
0161 target.add(item);
0162 }
0163
0164 /**
0165 * Init method.
0166 *
0167 * It initializes all the ArrayLists
0168 */
0169 private void init() {
0170 this.aandb = new ArrayList<T>();
0171 this.alpha = new ArrayList<T>();
0172 this.beta = new ArrayList<T>();
0173 this.del = new ArrayList<T>();
0174 this.ins = new ArrayList<T>();
0175 this.upd = new ArrayList<T>();
0176 }
0177
0178 /**
0179 * getInserted method.
0180 *
0181 * Getter method ins attribute
0182 * @return ArrayList<T> ins
0183 */
0184 public ArrayList<T> getInserted(){
0185 return this.ins;
0186 }
0187
0188 /**
0189 * getUpdated method.
0190 *
0191 * Getter method upd attribute
0192 * @return ArrayList<T> upd
0193 */
0194 public ArrayList<T> getUpdated(){
0195 return this.upd;
0196 }
0197
0198 /**
0199 * getDeleted method.
0200 *
0201 * Getter method del attribute
0202 * @return ArrayList<T> del
0203 */
0204 public ArrayList<T> getDeleted(){
0205 return this.del;
0206 }
0207}
En este caso, el tipo genérico es <T extends Historify>, que nos permite referirnos a "T" dentro del código como un tipo definido de clase, asegurando que será una subclase de "Historify".
Y con esto, tendríamos una implementación de un sistema DELTA en java!
Espero que os sea útil, o que al menos os sirva para ver nuevos conceptos!! :D
Os dejo el enlace al proyecto en GitHub, por si queréis verlo mejor.
Hasta la próxima!
No hay comentarios:
Publicar un comentario