jueves, mayo 13, 2010

UML, Agregacion y Composicion

He visto demasiadas discusiones vicentinas sobre las diferencias entre la agregación y la composición en los diagramas de clases de UML. Es más, cada cierto tiempo, alguien surge y me pregunta cual es la diferencia en el código y me explica sus propias teorías sobre esta cuestión, este debate es casi tan extenso como el de los extends y los includes de los casos de uso.
Empecemos, debemos recordar siempre que una de las mayores criticas que recibe UML, es que ha logrado salvar muchas ambigüedades... Pero no todas!!! aun quedan conceptos que se prestan a dobles interpretaciones, no se si este será uno de ellos, pero por lo discutido parece que sí. Por otro lado, podemos hacer un poco de historia, recordando que primero existió la asociación, después surge la Agregación para representar un relacion estructural contenedor/contenido y luego como una "extensión" de esta ultima nace la Composición.
Para explicar mi punto de vista voy a echar mano, al diagrama de clases que ya he utilizado en otro post y después voy a poner el código de la clase Persona, que es la que se lleva toda la carga de la discusiónEl código hace referencia, solo a este modelo, y es bien detallado, hasta con cosas innecesarias, o meramente teóricas, pero lo que busco es fijar una posición, concreta y definitiva, en el tema de las relaciones.
Algo importante a tener en cuenta, es que un objeto existe (digamos que esta vivo, pero esto no es técnicamente correcto por que no es un hilo) mientras existe una variable de referencia que "apunte" (tampoco correcto, por que java no tiene punteros, je) a dicho objeto en memoria. Es decir que se convertirá en elegible para ser borrado por el garbage collector, cuando no exista una variable que "apunte" a dicho objeto.


import java.util.LinkedList;
import java.util.List;

public class Persona {
private String nombre;
private String apellido;
private List perfiles = new LinkedList();
private List lugaresFrecuentes = new LinkedList();

//Setters and Getters
public String getNombre() {return nombre;}
public void setNombre(String nombre) {this.nombre = nombre;}
public String getApellido() {return apellido;}
public void setApellido(String apellido) {this.apellido = apellido;}

// OJO no confundir estos son solo setters y getters de las propiedades
public List getPerfiles() {return perfiles;}
public void setPerfiles(List perfiles) {this.perfiles = perfiles;}
public List getLugaresFrecuentes() {return lugaresFrecuentes;}
public void setLugaresFrecuentes(List lugaresFrecuentes) {this.lugaresFrecuentes = lugaresFrecuentes;}

La clase comienza normalmente con la declaración de las variables de instancia. Donde perfiles y lugaresFrecuentes son dos colecciones, pero tranquilamente podrían ser arrays. que se transformarán en contenedores de elementos. Al ser propiedades tienen getters y setters (accessors y mutators), que nada tienen que ver con la agregación y la composición.
Ahora veamos cuales son los métodos que caracterizan a la relación de AGREGACIÓN


public void agregarLugarFrecuenta(Lugar lugar){
if(!lugaresFrecuentes.contains(lugar)){
lugaresFrecuentes.add(lugar);
}
}
public void removerLugarFrecuenta(Lugar lugar){
if(lugaresFrecuentes.contains(lugar)){
lugaresFrecuentes.remove(lugar);
}
}

La primera característica es que la clase contiene dos métodos uno que agrega elementos a la coleccion y otro que los elimina de ella. He acá algo importante... los objetos son pasados por parametro, no han sido instanciados dentro del método, es decir no hemos realizado el new del objeto. Ha "nacido" en cualquier otra parte y se lo hemos pasado por parámetro al método para ser agregado a la lista lugaresFrecuentes. En otras palabras, el objeto Persona podria morir, y el objeto ahun podría mantener una referencia activa en alguna otra parte de nuestro codigo por lo tanto sus ciclos de vida no estrían atados. No nace ni muere, dentro de la Persona.

¿Cual es la Diferencia con la COMPOSICIÓN?


public void agregarPerfil(){
Perfil p = new Perfil();
perfiles.add(p);
}
//sobrecarga
public void agregarPerfil(String nombre){
Perfil p = new Perfil(nombre);
perfiles.add(p);
}
public void removerPerfil(int index){

perfiles.remove(index); // aca lo quitamos de la lista

}
Bueno... la composición también tiene los métodos para agregar y borrar. Pero...

"el new del objeto se realiza dentro del método agregar"


la instanciación del objeto p se realiza dentro del método agregar y la referencia no se devuelve (es void o boolean), la variable de referencia local va a dejar de existir una vez que el método se termine de ejecutar, y el ciclo de vida de esa instancia en particular va a quedar atada a la lista, y por ende a la Persona. Una vez que el objeto Persona no se referencie más, (o sea muera, aunque técnicamente esto no es así) el objeto lista, quedará sin referencia, y por lo tantos sus elementos también. Además como el método no es estático, se deberá crear primero una instancia de Persona, para después poder agregar un Perfil. Empezando así a "atar" el ciclo de vida de un Perfil, al de una Persona.
En cuanto al método remover, no existe nada de extraordinario, simplemente quitamos un elemento de la lista.

Volviéndonos Paranoicos de la Teoría


Profundizando la paranoia y jugando con la teoría; para que atemos definitivamente los ciclos de vida, la variable lugaresFrecuentes no debería ser una propiedad, y la clase Perfil debería ser una Inner Class.
En el caso de la Inner class, hacemos esto para que se tenga que utilizar una instancia de la clase "Outer" para luego obtener una instancia de la clase Inner. Por ejemplo si en nuestro codigo, la clase Perfil, fuera una Inner class de la clase publica Persona (Se entiende no?, es decir que esta dentro del archivo Persona.java), para obtener una instancia de Perfil fuera de la clase persona tendríamos que hacer:

Persona persona = new Persona();
Persona.Perfil perfil = persona.getPerfil(...);

El new de creación esta dentro del metodo agregarPerfil. La clase Perfil existiría mientras exista la instancia de persona. (ciclos de vida atados)
En el caso de que la variable de instancia lugaresFrecuentes no debería tener getters y setters públicos, esto se debe a que ningún otra clase, con excepción de la clase Persona, debería tener la oportunidad de mantener una referencia viva a un objeto del contenedor. Y mucho menos obtener toda la Lista desde afuera! Un objeto Perfil, vive y muere con la Persona!
También de esto se pueden desprender otros delirios, como cuestiones de herencia y cosas así.
Pero de nuevo, y no me voy a cansar de decirlo... esto es un extremo!!, es solo para conversar entre amigos, o vanagloriarse con algún profesor, no tiene nada de practico, ni de real, salvo para casos específicos.

Paranoia de la Paranoia


¿Creían que ya habíamos terminado? aun se puede ser más paranoico!!!! mucho se ha discutido sobre estos temas, y mucho fue paranoia teórica. Lo siguiente, es algo que les llevará a sus amigos o profesor a decir, "...bueno pero eso ya es una locura":
Sobreescribiremos el método finalize() de la Clase Persona, que es un método que todas las clases heredan de Object, y que se invoca justo antes de que un objeto sea borrado de la memoria por el garbage collector de java.

public void finalize(){
for(Perfil p : perfiles){
p = null;
}
}

En el desreferenciamos cada uno de los elementos de la lista un segundo antes de que el objeto de tipo Persona desaparezca de la memoria, una milésima de segundo, o algo asi!!! atando definitivamente el nacimiento y muerte, el ciclo de vida, de un elemento contenido con su contenedor.
Pero de nuevo!!! LA PARANOIA EN EL CÓDIGO NO ES BUENA!!! solo sirve en aquellas noches de borrachera entre programadores, en las cuales el boliche cerró y pinta quedarse en casa con amigos.

Yo rescataría de todo este biri-biri aquello de "el new se realiza dentro del método" y nada más!!!

21 comentarios:

Anónimo dijo...

Hola,
Lo difícil no es discernir entre agregación y composición, sino entre agregación y asociación. ¿Cómo se codificarían ambas relaciones? A mi modo de ver, es algo meramente conceptual.

Saludos,

aro dijo...

a ver.... voy a postear mi respuesta para que otros la puedan ver.
Saludos.

Frand dijo...

Simplemente Excelente.
Muchas Gracias…

Estaba buscando esto y no lo conseguí en ningún libro. Quisiera que pusieras el código que corresponde a Foto para distinguir una asociación y diferenciarla de la agregación.

Frand de Venezuela…

aro dijo...

Hola Frand, antes que todo gracias, el codigo de la Clase Foto no lo puse por que se deduce del diagrama y no tien complicaciones, son simplemente 3 vlbles de instancia:

private String pathToFile;
private String tipoArchivo;
private float tamano;

y los setter y getters

public void setPathToFile(String s){this.pathToFile = s;}
public String getPathToFile(){return this.pathToFile;}
.... etc

Saludos

Anónimo dijo...

seria correcto definir dentro de la clase Foto algo asi:

private Persona persona:

para saber quien es la persona dueña de la foto???

aro dijo...

es todo un cuestión de diseño amigo, si a tu modelo le sirve, así tendría que ser. Pero bajo mi punto de vista no parece correcto. Pensalo de esta forma..."una foto puede tener una persona?" o es simplemente una foto?. En cambio, una persona puede tener una foto (relación has-A)

Anónimo dijo...

Estoy muy agradecida de esta información.

Estoy intentando encontrar un Diseñador UML en el que se muestren las diferencias de la agregacion y la composición al momento de generar el codigo.

He hecho pruebas con Enterprise Architect y la nueva versión de UML de netbeans, mi idea es generar todo el codigo para que los programadores no nos confundamos de lo que se pide de codigo o de relaciones entre objetos desde el diseño, sin embargo no lo he conseguido...:(, en ambas relaciones siempre genera el mismo codigo.

Conoces algun software que si muestre las diferencias de codigo entre Agregacion y Composicion?

De antemano Muchas gracias por toda la información

aro dijo...

No la verdad es que no conozco un soft. Y tu objetivo, es el mismo que me llevo a realizar estos documentos jejej. La verdad es que una compocicion, como escribi, fue una derivacion de la agregacion, la diferencia es "ciclo de vida" eso armo todo el lio je. No creo que lo encuentres en un soft, por que esto todavia forma parte de una discucion y hasta que no se estandarice, ningun soft lo tendria que tener... salvo aclarando que es su interpretacion, por eso yo escribo diciendo que hay mucho hablado. Mi consejo, es que elijas un material sobre aquellos puntos conflictivos que tiene UML y lo definas como especificaciones de arquitectura para tu team. abrazo

Unknown dijo...

te agradezco mucho por esta informacion....

realmente es muy buena, esto no se consigue en cualquier lado y siempre trae confusiones...

muchísimas gracias!
saludos

aro dijo...

Thx... espero que aclara algunas dudas

Anónimo dijo...

Habrás visto explicaciones bizantinas (de Bizancio) y no vicentinas.

Muy buena la explicación, anyway

aro dijo...

ok fe de erratas: donde dice "vicentinas" (vaya a saber donde...) léase "bizantinas"... ejej se se ... no es el chocolate o la pólvora tampoco no? jeje saludos

Unknown dijo...

Excelente gracias por compartir

aro dijo...

Saludos!

MaTU143 dijo...

Muy buena la info!!,una pregunta: una clase puede estar compuesta de otra y simultaneamente tener relacion de agregacion con la misma? gracias!!

aro dijo...

Supongo a que te refieres a que si una clase puede tener una "clase interna" o privada, o algo asi. En java se llaman nested classes, y se hace la diferencia entre "Static Nested Classes" e "Inner Classes". En el segundo de los casos, las Inner, se no abría problemas para tener una agregación. Pero UML nos proporciona un artefacto especifico para este tipo de representaciones. Por que las primeras son estáticas por lo que solo tendremos una "copia" o "instancia" de dicha clase (La instancia de class).
Saludos

Unknown dijo...

Hola, m gustaría preguntarte por el constructor de Persona, ya que no se si se debe de incluir en el constructor las "list".
Por ejemplo si
Persona -------agregación------ lugar

Para:
1.n ---------- 1.n
Hay que crear pasarle la lista de lugar.

Gracias.

aro dijo...

Hola Java F
Los constructores en realidad se crean de acuerdo a nuestras conveniencias. Por lo general, y si son objetos POJOS simples, como los representados, solo se tiene un constructor vacío. La AGREGACIÓN en persona se va realizando de objeto en objeto. Asignarle toda la lista completa, es realizar el “Set” de la propiedad lugarFrecuente, como si fuera cualquier otra propiedad.
Saludos y espero que te haya servido

Pedro Arnoldo Machado Duran dijo...

Quee baaaarbaro maeeestroo te luciste es una gran explicación de composición y agregación. Solo una duda que es la asociacíón ya que en no vi la explicación que la clase foto con respecto a la clase Persona

saludos

Manuel Puebla dijo...


La variante de las Inner Class es muy restrictiva, pues no permite tener tener getters ni setters públicos, lo cual es una locura.

La variante del “finalize” no funciona en mi criterio, porque, aunque ponga los apuntadores a null, si alguien más estaba apuntando a esa zona de memoria el “garbage collector” no lo eliminará. Es decir, ponerlos a null no garantiza que se libere esa zona de memoria. En otros lenguajes como C++ sí es posible ordenarle al compilador que libere una zona de memoria, en JAVA hasta lo que sé esto no es posible hacerlo.

La variante que propongo es hacer el new dentro de la clase, tal como se propone aquí y todo método que necesite retornar un objeto de la clase, debe retornar copias del objeto, nunca debe retornar un apuntador. Esto se hace redefiniendo el método “Clone” de Object.

Saludos.

aro dijo...

Es por ello, que habla de ¡paranoia! Repetir que una locura es locura… es locura al cuadrado. Jaja. Pero el debate es interesante con una cerveza en la mano, algo como un prototype… lo tendría que pensar, no quiero que se me escape el ciclo de vida. Una nested, o inner, no tiene nada de extraordinario, su comportamiento debería mantenerse dentro de la clase Outer. Una referencia a una inner, estaría referenciando también a la outer. Por lo que sí existe por ahí, alguna referencia viva a la inner, es porque también existe de la outer, y el garbage no podría destruirla primero.
Tal como dices, lo que el garbage collector de java puede hacer… Solo “don” garbage collector de Java, lo sabe. Le puedes dar la orden, pero el responderá como quiera.