Paradojas I: Donde quedo la composición?
La composición (en OOP) es un modo de combinar objetos simples y crear objetos complejos.
Cuando un objeto es "Compuesto" se dice que tiene una relacion "tiene un". Un ejemplo del mundo real de composición seria por ejemplo, un automóvil (ejemplo tomado de wikipedia). Se puede decir que que un automóvil "tiene un/a" rueda, volante, asiento, motor, etc.
Hasta aquí todo bien... es un concepto simple,y en muchos casos se le considera una de las razón "clave" por la que la OOP es una "buena idea". Por lo tanto uno esperaría que un lenguaje como Java tuviera composición pero... la tiene? O solo tiene agregación?
La agregación, difiere de la composición en que no implica "pertenencia". En composición,cuando el objeto "dueño" es destruido, también es destruido el objeto contenido. En agregación, esto no es necesariamente cierto:
Por ejemplo (otro ejemplo tomado de wikipedia), una universidad tiene varios departamentos, y cada departamento tiene un numero de profesores. Si la universidad cierra, los departamentos dejaran de existir, pero los profesores en esos departamentos continuaran existiendo.
Ahora bien... existe un modo univoco de representar composicion vs agregacion en Java? en C++ seria asi (de nuevo, tomado de la wikipedia):
Se puede "decir" esto mismo en Java "en tiempo de compilación" ?
- luxspes's blog
- Inicie sesión o regístrese para enviar comentarios
Si se puede, sin embargo no
Si se puede, sin embargo no hay una diferencia en la sintaxis, solo en la forma en la que se usa.
Otro ejemplo de composición vs. agregación es, por ejemplo, un ser humano y la ropa que viste. La ropa que viste es una agregación a la persona, si se le quita, no pasa nada ( bueno nomás le da frío ) y composición pueden ser sus órganos, corazón, pulmones, etc. si la persona muere, aquellos dejan de existir también, si la persona no los tiene, la persona misma muere ( depende del organo claro ).
Un ejemplo en Java sería así:
Este código como resumen, se pueden hacer más y más cosas, como que el corazón sea una clase interna de la persona y solo la persona pueda crearlo, o que no haya setter para el corazón y en caso de haber un getter se devuelva una copia.
Entonces, en Java no hay una sintaxis especial para diferenciar la composición y todo depende de la forma en la que se use.
GC
Viendo el ejemplo de la universidad, tal vez llevo muchas horas sin dormir pero creo que todo se reduce a manejo de memoria. Si tienes alojada una University estática en memoria, tiene un arreglo estático de Department, por lo tanto la existencia de los departamentos depende de la universidad. Pero un Department tiene referencias a sus Professor, no los tiene de manera estática en el arreglo, por lo que si un Department deja de existir, no pasa nada con los Professor.
En Java (y varios otros lenguajes orientados a objetos) no hay manera de alojar objetos de manera estática, es decir, que estuviera el espacio de memoria reservado para el objeto, en el stack al iniciar la JVM, en vez de que se pongan en el heap cuando se crean (en cualquier momento durante la ejecución). Y como no existe un free() como contraparte del
, pues técnicamente sólo existe el patrón de agregación, porque cuando objeto que tenga referencias a otro, las seguirá teniendo hasta que sea recolectado. Y entonces puede que los objetos referidos también sean recolectados, si es que ya nadie más tiene referencias a ellos, o sigan existiendo, si alguien más tiene referencias a ellos. Es como si al destruir la universidad, se destruyen las facultades, y los profesores quedan volando; los que tengan una casa, familia, amigos, alumnos particulares, etc que hagan referencia a ellos, seguirán existiendo; los que están completamente solos en el mundo, llegará en la noche el GC como el coco y se los lleva.
@OscarRyz: El corazon sobrevive: No hay composicion en Java
Si yo escribo:
Agregacion: el corazon sobrevive, aunque la persona murio.
@Ezamudio: En C++ composicion == estatico, pero...
Tiene que ver con el manejo de memoria si... y efectivamente en C++ la composicion va "agarrada" del manejo estatico de memoria, y la agregacion del manejo "dinamico", pero creo que la abstraccion "composicion" o "agregacion" va mas alla del manejo de memoria... por que los lenguajes modernos no tiene un modo de decir (notese palabra clave inexistente en Java "has_a") :
de forma que aunque yo haga esto:
No nos llevaria esto (en muchos casos) a un manejo mas eficiente de la memoria, y por otro lado, a un aprendizaje mas facil de OOP (la mayoria de los programadores novatos que conozco esperan que el comportamiento sea el esperado para composicion y se sorprenden con el comportamiento de agregacion)
Nope, por que te manda
Nope, por que te manda NullPointerException porque en la linea anterior persona se asigna a null y null.getXyz() tira Npe.
Entonces, como está escrito, el corazón sería garbage collectado cuando la persona lo sea. Pero el sueter no, por que si hay una referencia externa a la Persona.
Es importante hacer más más robusta la implementación de persona( la que puse fue muy breve ); por ejemplo que no haya getter o que el getter devuelva una copia, a esto se le llama copia defensiva y no es necesaria con clases inmutable ( como Integer, Double, Boolea, o String , por que pues.. no mutan :P ) y es una característica deseable en la POO porque tampoco necesitan sincronización y toma este importante concepto de la programación funcional, donde nada es mutable ( y para hacerlo mutable estan las monadas ).
Ehem, pero volviendo al tema: "persona.getCorazon()" no existiría, así no se puede obtener una referencia del corazón que quede viva cuando la persona muera.
Ahora suponiendo que hubiera el getter podría hacerse así con copia defensiva.
Otra opción es tenerlo como clase interna ( lo mencioné en la respuesta original? )
Por lo que las instancias de B mueren junto con las instancias de A.
Pero en Java sí se puede tener composición, solo que no a nivel de lenguaje, sino a nivel de programación. Si la implementación tiene como efecto el uso en el heap o en el stack ya es otro rollo.
Correccion: Ahora si demuestra la no composicion
Perdon, se me fue el dedo, ya lo corregi, ahora si se "null-ifica" la persona y sobrevive el corazon
Es por eso que no debe de
Es por eso que no debe de haber getter o hacer una copia defensiva.
Si no hay getter no hay forma de que hagas el assert, porque no hay referencia al corazón.
Si se hace una copia defensiva ( con alguna de las opciones ) entonces, lo que tienes no es el corazón original, que muere con la persona. Sigue habiendo composición.
Más sobre copias defensivas en el libro Effective Java:
Acá encontré un extracto:
Encapsulacion no es composicion, pero la simula?
El titulo lo dice todo: Encapsulacion no es composicion
De acuerdo, la copia defensiva de ayudaria, mediante encapsulacion a simular composicion, pero no seria verdadera composcion (como en C++) por que X no podria tener al mismo tiempo una relacion de composicion con Y y de agregacion con Z (a Z la tocarian copias de X, pero nunca la X original, a menos que se definiera un metodo especial para permitirle acceso directo a Z)
Los conceptos de programación
Los conceptos de programación orientada a objetos no necesitan tener una relación 1 a 1 con la implementación en un lenguaje de programación determinado.
El ejemplo que puse, aunque breve es un ejemplo perfecto de composición y de agregación.
La persona esta compuesta por un corazón y agregada por un suéter.
No hay getter ni setter para el corazón y solo se crea dentro de la persona y la persona es la única que tiene referencia a él. Cuando la persona deja de existir, también lo hace su corazón. No hay contradicción con el concepto de composición.
El suéter por otro lado es independiente de la persona y la persona del suéter.
En el contra ejemplo que pusiste estas llamando a un método que no existe.
Si quieres forzar las cosas, se puede hacer que se devuelva una copia, la composición sigue, la relación que existe entre la persona y el corazón sigue igual.
Java no tiene una construcción específica para la composición en el lenguaje, de la misma manera que C++ no lo tiene para la la interfaz ( y me refiero al concepto de interfaz de la POO ) o que Smalltalk una construcción especial para ninguno de los dos. Pero no por esto es correcto decir que que en Java no se puede hacer composición, o que los objetos en C++ carecen de una interfaz publica o que ninguno de estos dos lenguajes soporta llamado de mensajes por que ninguno de los dos lo hace como Ruby o Smalltalk. Los conceptos están en un nivel de abstración más arriba de la forma en la que un lenguaje de programación X lo implementa, o no lo implementa para nada, pero este no es el caso en Java cuando se habla de composición.
Encapsulacion solo simula Composicion (y no muy bien)
Esa es una buena pregunta... lo necesitan? Pongamos el caso de que el Corazon tuviera un metodo "setColesterolLevel" que afecta su eficiencia.. en C++ no seria ningun problema empatar eso con composicion, pero en Java:
Y esto:
Si sirve, pero nos fuerza a crear un metodo en persona, por cada metodo que Corazon tenga (lo cual a mi no me late mucho, y menos si Corazon a su ves tiene composicion con sus ventriculos, y esos a su vez con otros componentes mas...)
Al simular composicion con encapsulacion, implicas que algo no puede contener a otro algo y al mismo tiempo tenerlo "a flor de piel", o, que si lo tiene a flor de piel, tiene que ser inmutable, pero composicion no implica que algo tenga que ser "interno" o inmutable: Al final, la simuacion daña la consistencia logica del modelo de objetos, complicando innecesariamente las cosas... imagínate cuan aun mas se complican si decides persistir el modelo con JPA...
Aunque sea más práctico en
Aunque sea más práctico en terminos de programación, no es muy correcto que la gente tenga expuesta su corazón así nada más para que cualquiera le pueda subir el nivel de colesterol. Como funciona es así:
Sé que se está poniendo absurda la discusión porque lo siguiente a sugerir será No hablemos de corazones porque en en software no se hace blah, blah blah. pero mi punto es que si lo que estas necesitando es que la gente ande con sus corazones expuestos para que les puedas subir el nivel de coresterol a voluntad, entonces lo que necesitas no es composición, sino agregación ( por ejemplo para que te puedan prestar su sueter )
Que es más padre, práctico, fácil hacerlo de otra forma en otro lenguaje possiiiiii pero eso no hace que ya no exista.
Puedes decir, no existe exactamente como en... o no sepuede hacer lo que acá si... etc. o puedes decir que no es perfecta porque en un mundo perfecto sería así y asá etc. pero de eso a que no existe me parece que hay mucho trecho
No lo puedes garantizar
Aunque tu ejemplo se me hace muy ingenioso, no puedes garantizar que no necesitaras composición de objetos mutables visibles... y por lo tanto, tienes que aceptar que el soporte simulado de composición con objetos mutables en Java complica innecesariamente las cosas.
Entonces ponlo de esta
Entonces ponlo de esta forma.
Persona - Corazón es un ejemplo de composición, si o si. Pero si la persona dona su corazón y se hace un transplante, el cuerpo muere y el corazón continua viviendo en el cuerpo de otra persona, y por esa razón ya no le quieres llamar composición sino agregación, entonces ya el problema está en otra parte :)
Si se obtiene una referencia al elemento de la composición ( el corazón ) y de esa forma se evita que se elimine cuando se elimina el objeto que lo compone se elimine ( la persona ) no es al lenguaje al que hay que culpar, ni por eso se deja de llamar esa relación composición.
Si en C++ al morir eliminar el cuerpo la referencia del corazón se vuelve inválida aunque yo la haya obtenido de una manera lícita ( getCorazon() ) y con eso se introduce un error sutil y casi indetectable en mi programa, pues.. allá yo y mi manejo de memoria en C++, pero no por esto se hace más o menos compuesta la relación entre esos dos objetos.
Los objetos mutables
Los objetos mutables complican las cosas en cualquier ámbito, no solo en la composición, también en la agregación, en la herencia, en la programación multithread y básicamente en todos lados. Es por eso que en Java, la clase java.lang.String es immutable, para que la puedas pasar a quién tu quieras.
El modelo de objetos de C++ es híbrido el de Java no
Las relaciones de agregación y composición (composite agregation) pueden muy bien implementarse tanto en C++ como en Java.
C++ maneja dos modelos de objetos: por valor y por apuntador o referencia. C++ trata de "acoplarse" lo más posible con C y por esa razón utiliza para los objetos el mismo modelo por valor y por apuntador que para los tipos primitivos y los tipos definidos por el usuario del lenguaje C.
Java por otra parte abandona el modelo de objetos por valor para los tipos definidos por el usuario (clases, etc) y se queda solamente con el modelo de objetos por apuntador o referencia.
Si deseo entonces implementar una relación de composición, donde identifico claramente un todo y varias partes, y una vida de las partes controlada totalmente por el todo, tengo en C++ dos modelos de objetos para hacerlo, mientras que en Java solamente tengo uno.
Tomando algunas de las clases de los ejemplos que han puesto, la sentencia:
dentro de alguna función, crea un objeto de clase Department en C++, mientras que en Java crea un apuntador . Esto entonces tiene consecuencias para modelar cualquier relación entre Department y digamos University.
Si trabajo en C++ con el modelo por apuntador:
con el equivalente en Java:
voy a tener una solución similar para implementar la composición tanto en un lenguaje como en otro.
Pienso que para la mayoría de los caso prácticos un código como el siguiente en Java implementa una relación de composición:
Al "matar" a grupoBaile, van también a "perecer" todos los bailadores; se quedan en el limbo hasta que gc decida mandarlos al paraíso o al infierno.
La restricción que tengo es que sí quiero manipular a los bailadores fuera de la colección lo tengo que hacer pidiéndoselos a la colección.
Si mi intención es tener vivo a los bailadores antes de inscribirlos al grupo, y así puedan inscribirse a otro grupo (una agregación simple) entonces los tengo que "ver" antes de incorporarlos al grupo:
Con C++ para el modelo de objetos por apuntador, el código sería similar.
Ja ja ja
Perdón, pero me hiciste la tarde con este ejemplo de código, morí de la risa XD
Dependiendo de como
Dependiendo de como implementes:
En el siguente codigo pues se aplica tu afirmacion:
En este otro codigo, el objeto original se vera afectado:
y generalmente un get se implementa de la segunda forma.
@Beto.Bateria: Si, pero eso es agregacion, no composicion
Te recomiendo volver a leer mi blog post y los comentarios de OscarRys, poner el get como tu dices abre la puerta a la agregación, y anula a la composición.
@OscarRys: mas agregada... menos compuesta?
No lo se... hacerla la mas agregada la hace menos compuesta? Si asumimos que son estados discretos y mutuamente excluyentes, al ser agregada deja de ser compuesta... al menos con respecto a un objeto en particular... o no?
Compilador
Puede que sea un buen tema a discutir para programación de compiladores, pero en la práctica (me refiero a programación de sistemas empresariales) ¿realmente se ocupa llegar a tanto nivel?. Vaya que yo concuerdo con lo que dice oscar, no necesito que el lenguaje me obligue a usar cienta sintaxis para implementar un patrón de diseño (o tal vez sí, depende el gusto del proramador), lo que si necesito es que me de la capacidad de poder implementarlo (aunque se ocupen pocas lineas de más). Ya sea de forma nativa o de forma simulada, a final de cuentas se obtiene el mismo resultado, de una forma practicamente elegante.
Sobres
@Nopalin: Ciertamente ingenioso, pero no elegante
La incongruente definicion de agregacion y composicion se considera, desde la perspectiva de otros paradigmas como uno de los errores garrafales de la OOP (pero esa discusion mejor la dejo para otro post).
En cuanto a la nececidad practica de un modelo congruente, JPA es un ejemplo de que agregacion vs compocision es una decision comun (Borre a la Universidad... debo o no borrar a los deptos y a su ves debo o no borrar a los profesores? Cual es el modo correcto de representar esto con los @Annotations de JPA?)
Prefieres que el compilador te obligue a utilizar otras características del lenguaje para mal tapar sus carencias mediante simulacion?
No son solo las lineas de mas, es el socavamiento del modelo conceptual de composicion/agregacion debido a limitaciones del lenguaje
Elegante? Tener que implementar una fabrica de objetos para simular composicion? Yo lo encuentro ciertamente ingenioso, pero no elegante
En tiempo de compilacion
En conclusion, en Java, aunque puede simularse "at runtime", no es posible representar composicion "at compilation time"... o alguien puede presentar un contra-ejemplo que invalide esta afirmacion ?
@bferro: Excelente y "al grano".
Me quedo con la explicación de bferro.
Al final de cuentas, el ser hybrido dá muchísimas otras cosas que se podrían discutir al compararlo con Java, por lo que se resume a conocer los modelos de manejo de memoria y hacer una implementación correcta para que no quede del lado del lenguaje únicamente.
Una pregunta más, bferro es Ud el Dr. Bárbaro Ferro? que dió clase en el Centro de Investigación en Computación del IPN hace aprox. 10 años? o solo coincide el nick?
saludos
En cuanto a la nececidad
Vaya, a lo que me refiero es por que se debe ser tan estricto. Ya sabemos que en java no podemos liberar memoria manualmente como en otros lenguajes, (lo hace la JVM dependiendo si hay objetos a los que ya nadie hace referencia), pero eso no evita que se pueda llegar a implementar composición. Por ejemplo, ¿por que sacarias el objeto corazon del cuerpo, si al hacerlo puede que este no se elimine al eliminar el cuerpo?, como dice oscar son cosas que evitas en el momento en que estás desarrollando la lógica de tu programa. Ahora, que tal si haces:
Al terminar la llamada al método se elimina la referencia al corazon y queda solamente referenciada por el cuerpo, asi si en otro metodo eliminas el cuerpo, se elimina el corazon, que es el comportamiento esperado.
Entonces, la composición se puede lograr en java, sea nativa o simulada, ¿cual es la diferencia?
sobres
Separando aguas
Creo que hay dos temas aquí, por un lado las implicancias lógicas y por otro las de implementación.
En términos de diseño lógico, la agregación y composición son útiles para definir como colaboran dos o más clases. La composición está diciendo, por ejemplo, la rueda podría o no "existir" separada del automóvil, pero lo interesante e importante es el automóvil (esa es la clase principal) y por lo tanto la rueda pertenece al automóvil y debería compartir su suerte (si se destruye el automovil se destruye la rueda, ahora se puede reemplazar una rueda y el automóvil sigue igual).
La agregación indica que las clases colaboran entre sí, pero son básicamente independientes. Por ejemplo, un vendedor de automóviles, o las dependencias de exhibición en una compraventa de vehículos.
Ahora como se implementa, puede haber varias formas correctas, pero lo que como analista se busca transmitir es que si despido un vendedor, no tengo porque destruir los automóviles que fueron vendidos por él. A la inversa, si vendo un automóvil, lo vendo completo (con sus ruedas, volante, motor, etc).
No es tan facil entender implicaciones verdad?
Si getCorazon esta implementado con el mecanismo de copias defensivas que expuso OscarRys, tu llamada a setColesterolLevel se queda sin efecto... si por otro lado, no estas efectuando copias defensivas, entonces la relacion con corazon es agregación al menos desde la perspectiva de quien invoque a getCorazon.
Parece que no es facil entender las implicaciones de las copias defensivas para simular composición... verdad?
Agregacion != Composicion != Colaboracion
Si la la rueda podría o no "existir" separada del automóvil, es agregación.
Si tu punto es que las relaciones entre los objetos en el mundo real son dinamicas, y varian con la perspectiva: Si destruyo al automovil destruyo la rueda, pero puedo cambiar la rueda, o destruirla a ella sola, estoy de acuerdo... Implica esto entonces, dado que en el mundo fisico, con la posible excepcion de los quarks todo es divisible, que la composicion no existe en el mundo fisico?
No, la agregacion es un concepto estructural, mientras que la colaboracion es dinamica, la definicion de la clase de un objeto puede no contener a otro y sin embargo puede haber colaboracion, inclusive existen patrones para tener colaboracion entre objetos con un alto grado de desacoplamiento.
De acuerdo con esto, pero eso no quita que en Java, basicamente todas las relaciones son de agregacion, y es necesario hacer "trucos", para "simular" un comportamiento de composición.
@OscarRyz: Si se puede, sin embargo no
En tu primer ejemplo solo me surgen una dudas:
) que se le agrega a la Persona... si no es así corrigeme vale.
Según yo debe de usar '' el sueter (
Está dicho casi todo
El tema casi se agota y me resulta difícil escribir algo nuevo, diferente a lo que se ha discutido. Coincido tanto con los que afirman que no puede escribirse en Java una composición "absoluta" como los que dicen que es posible hacerlo. Todo depende de las restricciones o invariantes que quiera escribir para los objetos que participan en esa relación.
A favor de que puedo escribir composición en Java
Cuando presento en mi modelo conceptual una composición entre el todo y sus partes, mi pretensión como cliente de esa composición, es operar con la interfaz que me brinda el "todo", y no con sus partes o componentes, de manera que al diseñar el todo, nunca incorporo en su interfaz algún getter para obtener alguna de sus partes.
Eso no quiere decir que el todo sea inmutable; puedo ofrecer algunas de sus propiedades al cliente. En el ya manoseado ejemplo de la Persona y Corazon el objeto Persona puede en efecto ofrecer propiedades sobre el estado de su corazón. Las clases Wrappers hacen eso. Cuando voy a mi cardiólogo, él interacciona conmigo para saber el estado de mi corazón. No me saca el corazón para examinarlo.
Cuando deseo trabajar con un flujo de I/O decorado en Java escribo:
Lo que quiero resolver en este caso para leer el archivo expediente.obj, lo satisfago usando los servicios que me brinda el objeto din. Así lo tengo como restricción en mi diseño. Por supuesto que cargo con algunas cosas que no me agradan, como aplicar din.read() para que din solamente delegue en el flujo básico sin hacer nada, lo que no sucede con din.readInt(). En ese caso dincolabora con su parte y hace su chamba.
Si, por otra parte, mi diseño me obliga a que necesito operar tanto con el decorador como con el flujo básico, entonces tendría que convertir esa composición en una agregación, manteniendo una referencia al flujo básico.
A favor de la no existencia de composición "absoluta"
Cuando la asociación entre dos clases, instanciada como un enlace entre objetos de cada clase, se implementa mediante un apuntador usando un modelo de objetos por referencia, es posible que otros apuntadores hagan referencia también a esos objetos. Si esa asociación es de composición, estoy obligado a "cuidar" que eso no suceda, y que solamente la parte sea vista y manipulada por el todo.
Si el modelo de objetos fuera por valor, la implementación sería otra como ya se ha discutido aquí.
Eres Manuel Estrada?
Eres Manuel Estrada?
@Rodrigo
Pero cual es la duda?
Si te refieres a que estoy usando
En vez de:
Es lo mismo, el punto ( o mi punto ) es que el sueter se le pasa ( se le agrega ) al objeto persona, mientras que el corazón, ya lo trae dentro.
:)
@bferro
Si si es Manuel Estrada.
Quizás no me explique bien
Efectivamente composición y agregación son conceptos estructurales, y el ejemplo del cuerpo se podría complicar un poco más, si por ejemplo, agregamos los trasplantes: en este caso el corazón sigue existiendo, pero la persona no. Ahora bien, el tema de colaboración es dinámico como dices, pero puede ser tanto interno como externo a una clase. Yo me refería al caso interno, en el caso de la persona, corazón y cerebro colaboran entre sí para que la persona exista (y pueda correr y comer y todo lo que hace una persona), ahora bien en el caso de la persona, la relación que me parece tiene más sentido es la de composición de los diversos organos corporales (dejando fuera los trasplantes por el momento), independientemente de su implementación.
Ahora pensando un poco el tema en Java, como la memoria la gestiona el Garbage Collector ¿podría suceder que ciertos componentes sigan existiendo después que la entidad principal haya sido liberada?, creo que en algún momento este comportamiento se daba y se producían memory leaks , pero las sucesivas implementaciones del GC han incorporado mejoras en los algoritmos y heurísticas que realizan la liberación de la memoria. Igual se puede ayudar al GC, por ejemplo, implementando finalize() y seteando explicítamente a nulo las referencias a los componentes con relación de (valga la redundancia) composición (esto último es debatible), además que no siempre es suficiente y hay que hacer más cosas (como eliminar registros de base de datos o cambiar estados, etc)
Aún así, mi punto va en que independiente de la implementación composición y agregación son conceptos de análisis y diseño, que transmiten información no sólo de la estructura de una clase, sino que además muestran (parte de) las interacciones y (parte) del ciclo de vida de las instancias de cada clase. Ahora bien, si Java permite "verdadera" composición o no, se puede discutir, pero la implementación debe ser consistente con el diseño y es responsabilidad del programador ver como lidiar con este problema.