domingo, diciembre 10, 2017

Principios SOLID, una conversación


Los principios SOLID datan de principios de siglo —del nuevo, no del viejo—, y establecen unas pautas generales para diseñar software más legible, mantenible y escalable. Hasta aquí, todo bien. SOLID son las 5 siglas, de cinco principios.
“S”, de  Single Resposibility principle, o lo que vendría a ser; que cada clase debe tener una sola responsabilidad. Si es de negocio, ¡es de negocio! no debe incluir lógica de comunicaciones. Nada de andar metiendo IFs en los controladores.
La “O”, de Open/Closed principle, es decir que los objetos o las entidades, deben ser abiertas para las extensiones, pero cerradas para las modificaciones.
—¡Me perdí, a otra cosa! ¿Abiertas? ¿Cerradas?
—Entiendo. Es difícil. Y muchas veces entra en conflicto con otros patrones de diseño. Algunas veces hay clases abiertas para su modificación ¿un Proxy, por ejemplo? y otras que no, que las tienes que extender.
Que hagas las clases de modo que cuamplan con un comportamiento, y que si este comportamiento se amplía, crees otra clase para ello.
Por ejemplo, si tu jefe te pide que le cambies el comportamiento de algo, no modifiques la clase existente, ¡ya pasó testing! construye otra. Y a ésta otra, la haces heredar de la primera y pones todo el código nuevo ahí.
Crecer por agregación, no por remplazo.
—Pero, ¿y si alguna clase ya no me sirve? ¿Se ha quedado obsoleta?
—¡Ahí entra el refactoring! Mueve el código a una clase padre, estableces una nueva línea base (?) y la cierras para su modificación
“L” de Liskov Substitution Principle, ««Olé, por Barbara Liskov»».

Este principio de sustitución sostiene que una referencia a una clase padre abstracta, puede ser cambiada por una referencia a una clase “hija” sin necesidad de conocer sus diferencias. Mmm…
¡Vale! En otras palabras significa, que una clase padre no debe contener comportamientos o variables que una clase hija no pueda contener. Por ejemplo, una referencia a una variable del tipo “MedioDeTransporte”, no puede ser remplazada por otra de una clase hija, “Coche”, si la primera tuviese un método “volar”.
Sí pones comportamientos en la clase padre que no son compartidos por el 100% de las clases hijas… ¡Hay tabla! ¡Modifica tu árbol de herencia!
La “I”, viene de Interface Segregation Principle, o lo que en la lengua de Cervantes sería; romper las interfaces grandes en pequeños pedacitos. Nada de métodos @Override vacíos con TODOs que manchan la pantalla. Si tienes una interfaz general que contiene todo el comportamiento, la “Segregues” en varias interfaces pequeñitas. Así solo declarar la tarea que vas a realizar, y en un futuro cercano, reemplazar solo ese comportamiento y no todo.
Por último, la “D” proviene de Dependency inversión principle. Este principio me parece similar o tiene algo que ver con el principio “L”. Ya que hace referencia a que la interacción entre todos los módulos (controladores, servicios, repositorios de objetos, utils) deben ser del tipo interfaz o clases abstractas. Nada de clases concretas. Pero además introduce otro concepto; el que tiene que haber una clase en el medio, que cumpla con algún patrón de creación —Factory method, Abstract Factory, prototype, etc—, que sea el responsable de realizar los “new” (Instanciar los objetos)
—¡Más clases! ¡El código se vuelve imposible de seguir! ¡Cómo lo debbugeo!
—Ya, sí... en un principio se vuelve más difícil… Pero te tengo una sorpresa…
—¿Cuál?
—A todo esto último… lo realiza Spring, cuando escribes la anotación @Autowired. Tú solo preocúpate por declarar bien tus variables.
—¡Tantas clases no se justifican, la mayoría de las veces!
—Es por eso que no debes apagar el cerebro. Piensa cuáles funcionalidades pueden cambiar en el futuro y cuáles no.
—¡Las del core del negocio!
—¡Exacto! Recuerda; clases con una sola responsabilidad, interfaces pequeñas… sobre las variables; que sean interfaces o abstractas. Crece por agregación, y sobre lo último… bueno, déjaselo a Spring o analízalo con más detalle.


jueves, mayo 10, 2012

MVC en Java - Model View Controller - Modelo Vista Controlador

Bueno, un tema complicado para escribir ya que se ha escrito demasiado, sobre algo que a mi parecer no tendría mucha relevancia, bueno sí, pero personalmente prefiero otras estructuras de organización de software. En fin, uno de mis amigos, me ha pedido que explique este Patrón de Diseño. Y que por su fama, y buen marketing  de software ha dado origen a otros patrones como el MVP (Model View Presenter)  y todos los model +view +something, Observer, etc. Además, este tópico, ha dado origen a las discusiones de café más apasionadas sobre los patrones de diseño de software y su utilidad real en el desarrollo actual.
Recuerdo haber leído algunos libros y ver representaciones del MVC, pero todos ellos mostraban en sus clases de la capa vista, una referencia a alguna clase de la capa modelo, o viceversa. Pero… y entonces…. tengo una duda; no era que el MVC era una “¿programación en 3 capas?”, y si hablamos de capas, por que existen referencias cruzadas.  Bueno, después de exhaustivos y apasionales debates sobre si el modelo debía considerarse una capa transversal a toda la arquitectura (y algunos otros argumentos más alocados), descubrimos, que el MVC  no fue igual toda la vida (es más hasta el día de hoy encuentro tantos MVC como arquitectos). Pero no todo es tan relativo, se mantiene algunos principios de responsabilidad de las capas.
Inicialmente y a grande rasgos, podemos decir que existen dos formas de MVC. Y la diferencia básica entre el primero y el segundo, es que en el primero (versión inicial), no se tiene una variable de referencia a alguna clase del “Modelo” en la capa de la vista, como mencione anteriormente.
Realizaré la presentacion en dos post, el primero, será el MVC inicial y el segundo el que tome en cuenta las relaciones entre el modelo, y la vista, y por que evoluciono de esta forma.

MVC + V1

La comunicación de cada capa con la inmediata superior, es lo que llevó a mucha gente a llamar a esto “programación en tres capas”. Como se distingue en las relaciones del diagrama, no existe una comunicación directa entre el modelo, y la vista.  Las referencias a interfaces y no directamente hacia las clases, nos permitirán un crecimiento por agregación (crear una clase y agregarla a nuestro software) y no por modificación. Así por ejemplo si en lugar de instanciar en el controlador un JFrame, instanciaramos un JInternalFrame, solo tendríamos que crear un Internal Frame e implementar la interfaz, sin tener que modificar el controlador mas que para cambiar la referencia.  En esta forma de orgenizar el código, solo se utilizan “objetos del lenguaje” (Map), y no "objetos del modelo" (Persona), y es esta característica, la que le dá la pureza de la separación entre capas.

El codigo fuente Java de esta Ejemplo se puede encontrar aquí!

Algunas consideraciones

Como vemos cada una de las clases tienen una referencia a una interfáz, y no a una clases concreta. También se lo puede hacer con clases abstractas, pero debido a la posibilidad de herencia múltiple de interfaces (Una interfaz puede heredar varias interfaces) e implementación múltiple que tienen las clases (Una clase puede implementar varias interfaces), en Java, nos conviene utilizarlo de esta forma para ganar en flexibilidad para agregar y sacar métodos

  ...
  private IControlable controler; 
  public FrmPersona(IControlable controler) {
    super();
    this.controler = controler;
    initGUI();
   }
  ... 
CtrlPersona.java
  ...
  private IDatable myView;
  private IActualizable myModel;
  ... 
Persona.java
  ...
  private IControlable controlador;
  public Persona(IControlable controlador){
 this.controlador = controlador;
  }
  ...

Vista - "Las pantallitas"1

La vista tiene como responsabilidad mostrar  y tomar datos. Las vistas no conocen nada de enteros, o tipos de dato. Todo para ellas son Strings. Con el desarrollo de las tecnologias web, se pensaba en poner validaciones de formato en esta capa (numerales, etc) para no tener que ir hasta los servidores y disminuir tanto performance como experiencia de usuario, pero con las tecnologias asincronas como ajax, esto pronto dejó de ser nacesario. No realiza ningún tipo de validación de negocio, ya que es el modelo el encargado de realizar dichas validaciones. Cada una de las acciones realizadas es una llamada al controlador para que él sea el encargado de tomar cualquier decisión.
  ...
  @Override
  public void setData(Map data) {
      this.txtNombre.setText(data.get(CtrlPersona.NAME));
      this.txtApellido.setText(data.get(CtrlPersona.SURNAME));
      this.txtEdad.setText(data.get(CtrlPersona.AGE));
  }
  ...
 @Override
 public Map getData() {
   Map data = new HashMap();
   data.put(CtrlPersona.NAME, this.txtNombre.getText());
   data.put(CtrlPersona.SURNAME, this.txtApellido.getText());
   data.put(CtrlPersona.AGE, this.txtEdad.getText());
   return data;
 }
 ...
 @Override
 public void actionPerformed(ActionEvent arg0) {
   if(arg0.getSource() == this.btnCancel){
     controler.cacelAction();
   }
   if(arg0.getSource() == this.btnOK){
     controler.oKAction();
   }
 }
 ...

Controlador - "Eso que entedes vos y nadie más... ponelo si querés...pero no pierdas tiempo.. bla bla...bla... cerveza!"1

En la capa de controlador, podemos encontrar que es donde se instancia las interfaces y modelos que el controlador quiere manejar. Una alternativa a esto ultimo, es que las instancias (Tanto de la vista, como del Modelo) sean pasadas como parametros al constructor del controlador.
Algunas de las responsabilidades de esta capa podrían ser las siguientes:
  • Realizar validaciones de formato
  • Subir y bajar variables de sesión
  • Redireccionar y hacer el forwared de las pantallas  y manejar el ciclo de vida de las Interfaces de usuario o sistemas.
  • Capturar tanto las excepciones de negocio, como de formato.
  • Tomar y poner información en las interfaces
  ...
  {
    myView = new FrmPersona(this);
    myModel = new Persona(this);
  }
  ...
  public void oKAction() {
    Map data = myView.getData();
    try{
        ParameterTool.checkMandatoryFields(data);
        ParameterTool.checkInteger(AGE, data.get(AGE));
        myModel.save(data);
    }catch(MenorDeEdadException e){
        setReturningMessage(e.getMessage());
    }catch(IllegalArgumentException e){
        setReturningMessage(e.getLocalizedMessage());
    }
  }
 ...

Modelo - "El diagramita de clases"1

La capa del modelo, es aquella que representa el negocio de la aplicación. Son aquellas clases con estructura de Java Beans que tanto conocemos. En un MVC la lógica de guardado de la base de datos suele estar en las clases del modelo, por supuesto haciendo llamadas a métodos de alguna librería o un paquete de utilidades para conectarnos con las bases. Es la capa que con las sucesivas iteraciones (y evoluciones a stacks de software) ha ido subdividiéndose, en subcapas, como por ejemplo la de repositorios de objetos.
Una responsabilidad de implementación definida e interesante de destacar es la de chequear las reglas del negocio.

Bueno, existe mucho más que se puede decir de este patrón, ya que como dije al principio, se ha hablado mucho. Pero esto es lo que considero importante para poder aplicarlo, no solo de una forma académica, si no también implementable.  En el próximo post escribiré de la evolución del MVC y esto será en los próximos N días.  (O tal vez  N a la M días).
Mi pasión siempre ha sido no solo hablar teóricamente de algo, porque de eso seguro hay mucha gente que puede hablar más y mejor, pero al final del día… siempre soy solo un programador… y quiero ¡ver pantallitas funcionando!

1) Nota: traducidos a lenguaje "jefe" para la fácil adaptacion a todo tipo de públicos.

jueves, septiembre 22, 2011

Arquitectura de Software vs. el Caos

La verdad que he tenido ganas de escribir sobre este apartado hace un largo rato ya, pero bueno, es tan larga la tela para cortar que no sé bien por donde comenzar. Hoy... acá... más bien definiré cuales son mis impresiones particulares sobre este tema a nivel general, otro día definiré cuales son, bajo mi puto de vista, las formas de organización de los proyectos más adecuados, y una que otra técnica que nos puede ayudar.
Definir una arquitectura, es casi tan difícil como definir que quiere decir arquitectura. Si la misma IEEE, establece sobre arquitectura que no sabe a priori que es, pero que cuando se la vé se la reconoce. Escuchamos, por ahí, a diario en nuestras empresas, que nos dicen: “esto es problema de arquitectura” o “no se puede escalar... la arquitectura no nos lo permite” o algún que otro enunciado apocalíptico que nos deja pensando.
En el mundo informático, o por lo menos acá en argentina, existe una alta rotación de personal en las empresas informáticas, y tenemos que cambiar de entorno laboral con frecuencia. Y cada vez que que nos sumergimos en un nuevo proyecto, encontramos que abrimos el IDE, hacemos un check out de la aplicación y cuando volvemos con taza de café en mano, empezamos a achinar los ojos y a escrolear el árbol de paquetes en el explorador de proyectos, de la misma manera que un peluquero novato mueve el pelo de un lado al otro sin tener idea de por donde comenzar.
Punto para el caos.
Luego después de unos primeros días de total incertidumbre, y de esfuerzos vanos de nuestros instructores por explicar los inexplicable, realizando capacitaciones teóricas, de lo que escasas veces vamos a encontrar en en la aplicación, nos empezamos a preguntar por que no utilizamos algún ultra-mega-re-contra-probado componente que existe ya en el mercado en lugar de tratar de reinventar la rueda trabajando con nuestro rustico-humilde-eterno-beta componente para realizar la misma tarea?
Punto para el caos.
Bien, ya superamos nuestra angustia, y nuestro sentimiento inicial de junior--, ahora es tiempo de realizar el aporte que dejaremos para siempre como una impronta, como una marca de hierro, en nuestra empresa. ¡Vamos a desarrollar un nuevo modulo de negocio para nuestra empresa! ¡Vamos a sentirnos productivos! ¡Ya estamos en condiciones!. Empezaremos por leer la documentación para entender a que APIs le vamos a “pegar” y las capas sobre las cuales nos “pararemos” para construir. Abrimos el repositorio de documentación, si existe, y ...¡WTF!...vemos que está totalmente vacío o con documentación desactualizada.
Otro punto para el Caos, y podría seguir así por un largo rato.
A esta altura del post, podes estar sintiéndote frustrado, ya que no ves una linea de código, ni documentación técnica que diga como hacer tu arquitectura infalible. Solo viste problemas humanos, sentimientos, emociones, frustraciones. Nada técnico. Púes bien. La mayoría de los arquitectos que he conocido, era personas sumamente capacitadas técnicamente, gurues de la tecnología, hombres que se juntaban a tomar mate con neo en la misma matrix. Pero con, con escasos skills de comunicación y poco human-oriented. Hombres que se sentaban en su propio box en un estado de autismo del cual sacarlo sería pecado capital. Lapidación!
Dies puntos para el caos!

“El silencio de un arquitecto, es el peor enemigo de la escalabilidad de un software”

Somos arquitectos, queremos arquitectear!

Bajo mi puto de vista, cada una de las palabras que vaya a citar acá debe ser tomada en cuenta como un test case, el cual cada diseño que realicemos, cada aporte, cada linea, cada acción debe superar.

¡Responsabilidad!

Todo tiene que tener una responsabilidad acotada y definida, cada clase, cada paquete, cada método, cada proceso, cada documento. Se tiene que poder definir rápidamente los inputs y los outputs, y a que se dedica. Esta es la principal palabra de una arquitectura soñada. Si hacemos un método, nos tenemos que preguntar antes de la primera linea de código, ¿a que se va a dedicar este método? En lugar de escribir una cascada de ifs de 100 lineas de código. Ningún método, clase, paquete, etc, etc, debe ser superman, y hacerlo todo. Si tenemos métodos de 300 lineas de código, algo estamos haciendo mal. ¿Estamos trabajando Orientado a objetos, o estamos estoreando lineas de código en un archivo? ¿Cada capa se ocupa de lo que tiene que hacer?

¡Narcisismo!

¿Es nuestro arquitectura narcisista? A la cual podemos ver como un espejo y decirnos lo buen programadores que somos. Lo genios, intelectuales, que utilizamos reflection, recursividad o algún otro recurso “mágico” para realizar una tarea. ¿Son estas tecnologías necesarias? Imaginemos que después de nosotros va a venir otro programador que se va a tener que que pelar los dedos con F5 para poder debuggear nuestro código, en idas y vueltas. ¿Tenemos una arquitectura de 100 capas que nos dará una futura y muy lejana escalabilidad y reutilización? Pero si nuestra empresa tiene una alta rotación de personal, y los requerimientos siempre corren contra el reloj... “the new guy”... va a realizar todos esos métodos? Primero que eso... va a conocerlos? O va a buscar un workaround de código que le permitirá cumplir con los plazos del burndown?. Estudiando nuestra empresa, y sus características, sabremos como se comportará nuestra arquitectura en el futuro. Complejidad innecesaria = narcisismo.

¡Aburrimiento!

Toda idea que se nos cruce para realizar un componente, es probable que ya haya sido pensada. Debe existir por ahí. Bien, frente a una cuasi igualdad de características técnicas, ¿cual de todos los componentes utilizar? ¡el más nuevo, lo nuevo es mejor!
10 punto más para el caos!
Trabajamos en una empresa inmersa en un mercado laboral, con un conocimiento circundante, ¿debemos realmente malgastar tiempo de un recurso en capacitación sobre ese nuevo componente, que desconocemos, del cual existe escasa documentación, o que no tiene una comunidad de soporte amplia al rededor del mundo? O debemos escoger, ese que en una entrevista laboral al preguntar al postulante si ya tiene experiencia en el, nos conteste con una sonrisa y un rotundo - ¡Si! Es probable que ese componente tenga una comunidad amplia al rededor del mundo, con actualizaciones constantes, con documentación en nuestro idioma y cientos de blogs con tips para realizar una tarea. Lo demás, lo nuevo, lo aprendamos en casa, allí podremos hacer pruebas en nuestro sandbox personal sin afectar el proyecto. No agreguemos factores de riesgo, por mas aburridos que estemos.

Comunicativa

¿Es nuestra arquitectura capaz de darse a entender por si misma? De tal forma que solo nos necesite como meros heraldos de presentación. Que al abrir el proyecto nos brinde una idea general de donde esta cada cosa. Que a la hora de que alguien tenga que escribir un método sepa cual es la clase, o la capa en la cual debe ir. ¿Documentamos lo que hacemos? Los frameworks y componentes que mayor éxito tienen en el mercado son aquellos que mejor documentados están. Nadie quiere perder preciosas horas de investigación averiguando que tenia en la cabeza el programador a la hora de hacer el código. Siempre nos repetimos a nosotros mismos, y muchas veces sin razón alguna, más allá de la falta de documentación, que sería mas fácil hacerlo de nuevo que continuar el trabajo de otros. Las wikis suelen ser herramientas muy útiles a la hora de mantener tan “viva” nuestra documentación como nuestro código.
El conocimiento es una de las bases del poder, y teniendo en cuenta esto, existen personas en las empresas que buscan ser los “únicos” que entiendan la forma de hacer las cosas. Pero después, estas personas se marchan buscando nuevos horizontes laborales, y el conocimiento se pierde, y la semilla del caos ha sido sembrada. Es por ello, que documentar no solo se transforma en una ventaja competitiva, si no también en una necesidad de supervivencia.

Autocrítica

Nada es perfecto, todo es perfectible en el tiempo. Pero al igual que un rió, el código fluye. Los requerimientos cambian, el conocimiento se actualiza. Debemos criticar con periodicidad nuestro código. Lo que la industria llama “Refactorizar”. Esto es como agregarle agua a un pozo tapado con tierra. Siempre nos va a dar lugar para agregar un poco más, y va a hacer a nuestro código mas solido. De lo contrario, la deuda técnica se incrementará dia a día, y solo nuestro esfuerzo servirá para pagar los intereses de esta deuda. Nos debemos preguntar, esta clase se tendría que llamar así? ¿Estos métodos subclaseados, podrían subirse al parent en el árbol de herencia? Estos métodos de la misma clase comparten muchos código, ¿no debería sacarlo a uno private y parametrizarlo? Debemos crear ciclos de desarrollo-refactorizacion-desarrollo etc. Es como el chequeo médico para determinar la salud de nuestra arquitectura.

Eutanasica

Debemos entender que cualquier arquitectura que diseñemos tendrá un ciclo de vida. Se realizará para un momento determinado del tiempo. Con un conocimiento sobre el mercado, una visión empresarial y un nivel de conocimiento técnico determinado. Las grandes arquitecturas viven por años, aquellas que son chequeadas, analizadas, y tratadas periódicamente ante cualquier síntoma. Otras, no soportan la deuda técnica y se cancerizan rápidamente en el lapso de uno o dos años. Nos debemos preguntar, ¿donde estaremos en seis meses a este paso? ¿Podemos liderar el mercado técnico con esta arquitectura?¿podemos alcanzarlo?¿Ha cambiado la visión de la empresa? ¿hemos aprendido suficiente? Si estas preguntas no pueden ser resueltas rápidamente, o si no nos gusta la respuesta, es hora de ir preparando una “muerte digna” de nuestra arquitectura. Ir planificando una migración, dejar un grupo de soporte, mientras una nueva esta en camino. No esperar hasta que el agua nos llegue al cuello.

Son varios los items de la check list, pero es un principio.

Conclusión

Pensar en una arquitectura como algo meramente técnico, es partir desde bases erróneas. Por supuesto que un arquitecto debe ser una persona técnicamente idóneo, ya que debe entender lo que su equipo técnico esta sugiriendo, y saber tomar decisiones técnicas todos los días. Pero pensar que eso es los más importante, o que es lo único que debe tener, es concebir que el mejor albañil que conocemos es capaz de diseñar el empire state. Debemos entender que en este edificio abstracto que vamos a diseñar será habitado por personas, y no mayoritariamente por clientes, si no más bien por los desarrolladores, y toda clase de staff informático con lo que convivimos diariamente. Abramos las puertas, preguntemos como quieren vivir. Revisemos nuestra empresa, nuestro proyecto.

martes, junio 08, 2010

UML: Diagrama de Clases, ejercicio 1

Acá les dejo un ejercicio que le preparé para un amigo. Por si lo quieren realizar. La respuesta la voy a postear en algunos días.

Ejercicio:


El objetivo, es realizar el código java del diagrama que figura a continuación. Los métodos, salvo aquellos de la agregación, pueden contener solo la firma, no hace falta la lógica.


Respuesta

by Charli Quiroga

Clase Vehículo



import java.util.List;

public abstract class Vehiculo implements Desplazable {

private double velocidadPromedio;
private int velocidadMaxima;
private List ruedas;
public Vehiculo() {
super();
}

public double getVelocidadPromedio() {
return velocidadPromedio;
}
public void setVelocidadPromedio(double velocidadPromedio) {
this.velocidadPromedio = velocidadPromedio;
}
public int getVelocidadMaxima() {
return velocidadMaxima;
}
public void setVelocidadMaxima(int velocidadMaxima) {
this.velocidadMaxima = velocidadMaxima;
}
public List getRuedas() {
return ruedas;
}
public void setRuedas(List ruedas) {
this.ruedas = ruedas;
}
public void agregarRueda(Rueda rueda) {
if(!ruedas.contains(rueda))
ruedas.add(rueda);
}
public boolean quitarRueda(Rueda rueda) {
return ruedas.remove(rueda);
}
public abstract void romperInercia();
}

Interfaz Desplazable



public interface Desplazable {
public abstract void esquivarObstaculo();
}

Clase Rueda



public class Rueda {}

Clase Barco



public abstract class Barco extends Vehiculo{}

Clase Auto



public abstract class Auto extends Vehiculo{
public static final int N_RUEDAS = 4;
}

Clase Moto



public abstract class Moto extends Vehiculo {
public static final int N_RUEDAS = 2;
public void esquivarObstaculo() {}
}

Clase Boing747



public class Boing747 extends Vehiculo{
private static int viajes;
@Override
public void romperInercia() {}
@Override
public void esquivarObstaculo() {}
public static int getViajes() {
return viajes;
}
public static void setViajes(int viajes) {
Boing747.viajes = viajes;
}
public void despegar() {}
public void aterrizar() {}
public static void agregarViaje() {}
}

Clase BarcoAVela



public abstract class BarcoAVela extends Barco {}

Clase FordFalcon



public class FordFalcon extends Auto {
@Override
public void romperInercia() {}
@Override
public void esquivarObstaculo() {}
}

Clase HondaXR600



public class HondaXR600 extends Moto {
@Override
public void romperInercia(){}
@Override
public void esquivarObstaculo(){}
}

Clase HondaXR25



public class HondaXR25 extends Moto{
@Override
public void romperInercia(){}
public void esquivarObstaculo(int metros){}
}

Más info sobre Diagramas de Clases en la etiqueta Diseño

viernes, mayo 21, 2010

UML, Asociacion y Agregacion

El post de la Agregación y Composición despertó otra pregunta más (Como siempre suele suceder, ya dije que el debate siempre dá para algo más). La duda viene por el lado de la diferencia entre asociación y Agregación en código. Se sostiene que es algo conceptual, que no se representa en código. Mi respuesta es que esto es 50% correcto. Ya que, como mencione antes (la etiqueta Diseño tiene mas sobre esto), la Asociación surgió primero, y la Agregación vendría a ser un tipo particular de Asociación. Ergo ...Agregacion "IS-A" Asociación ¡punto para ud! . Primero veamos el diagrama UML

Ahora veamos el codigo de la clase Persona (Nota: imagínense los generics por que blogspot los toma como tags, así que no aparecen)

import java.util.List;

public class Persona {

private String nombre;
private String apellido;

private Foto foto;
private List lugaresFrecuentes;
private List comunicaciones;

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;}

//Asociacion Foto
public Foto getFoto() {return foto;}
public void setFoto(Foto foto) {this.foto = foto;}

//public List getLugaresFrecuentes() {return lugaresFrecuentes;}
//public void setLugaresFrecuentes(List lugaresFrecuentes) {this.lugaresFrecuentes = lugaresFrecuentes;}

//Agregacion
public void agregarLugar(Lugar lugar){
lugaresFrecuentes.add(lugar);
}
public boolean quitarLugar(Lugar lugar){
return lugaresFrecuentes.remove(lugar);
}
//Asociación
public void setComunicaciones(List comunicaciones) {
this.comunicaciones = comunicaciones;
}
public List getComunicaciones() {
return comunicaciones;
}

}

Las diferencias principales son que:
  1. ¡La Agregación son siempre colecciones, o arrays! O algo que sirva de contenedor para "agregar" más de un objeto, aunque agreguemos uno solo. (si no sería settear y no agregar, add)
  2. La Agregación cuenta con dos métodos: uno para "agregar" un solo objeto a la lista, y el otro para quitarlo de la misma.
  3. La agregación puede, como no, tener los metodos setter y getter, mientras que la Asociación siempre los tiene, que ponen y obtienen una variable de referencia del mismo tipo de la variable de instancia o de clase, en este caso List.

Bueno, espero haber limpiado alguna duda, y abrir otras ;) Saludos.

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!!!