Feeds:
Entradas
Comentarios

Archive for 20 febrero 2012


(Aviso a navegantes: ¡Este post contiene mucho código escrito a altas horas de la noche!)

Hola de nuevo, en este post, el último sobre “extends”, voy a intentar mostrar alternativas a la herencia de implementación. Se trata de la técnica genérica llamada “composición y delegación”, técnica en la que se basan varios patrones de diseño, como el famoso “Command”, el “Strategy”, o el “Decorator” entre otros. Antes de nada voy a resumir lo contado hasta ahora. En el primer post vimos cómo usar la herencia de implementación de forma “ingenua” nos podía meter en un buen lío, ya que sin darnos cuenta podemos romper la encapsulación. En el segundo post intentamos arreglar los problemas usando la palabra reservada “final” y el patrón “Template Method”. Si bien al principio parecía que funcionaba, más tarde al ir añadiendo funcionalidades, nos dimos cuenta de que violábamos el principio DRY y se producía una explosión combinatoria de clases, además de dificultarnos el testing y tener que planear de antemano los “ejes” de extensibilidad de nuestra clase.

El problema más importante es la explosión combinatoria de clases y la violación del DRY. La base de este problema es que la lógica de filtrado, y de postprocesamiento de la inserción de elementos, no se encuentran debidamente encapsuladas y abstraídas, sino que están incrustadas dentro de las clases hijas de “ListaSencilla”.

Empezando por la lógica de filtrado, ¿no sería mejor si pudiéramos tener una clase donde realmente pudiéramos poner dicha lógica? De esa forma podríamos eliminar ésta de las clases “ListaAuditableConElementosSinEspacios”, “ListaAuditableConElementosLongitudPar”, “ListaConElementosSinEspacios” y “ListaConElementosLongitudPar”. Está claro que estas clases violan el principio de única responsabilidad, ya que implementan el contrato de “Lista” y además tienen lógica de filtrado. Si sacamos esa lógica de filtrado aparte, vamos a quitar responsabilidad de dichas clases y a simplificarlas.

Lo primero de todo sería definir una interfaz “EstrategiaDeFiltrado” que represente el contrato entre una implementación de “Lista” y la lógica de filtrado.

public interface EstrategiaDeFiltrado {
  boolean esInsertable(String elemento);
}

Sencillo, ¿verdad? Ahora sólo tengo que tener una implementación por estragia de filtrado en mi sistema. Primero para los elementos pares:

public final class FiltrarElementosLongitudImpar implements EstrategiaDeFiltrado {
  public FiltrarElementosLongitudImpar() {
    super();
  }
  @Override
  public boolean esInsertable(String elemento) {
    return elemento.length() % 2 == 0;
  }
}

Y ahora para los elementos con espacios:

public final class FiltrarElementosConEspacios implements EstrategiaDeFiltrado {
  public FiltrarElementosConEspacios() {
    super();
  }
  @Override
  public boolean esInsertable(String elemento) {
    return !elemento.contains(" ");
  }
}

Nótese ese “final” para cada clase. Ahora nadie podrá usar herencia de implementación de dichas clases y provocar el caos. Es también interesante notar que con este diseño puedo hacer unos tests muy sencillos de cada implementación de “EstrategiaDeFiltrado”. Esto pinta bien, pero aun no hemos arreglado nada. Vamos a ver como reutilizar estas implementaciones de “EstrategiaDeFiltrado” en nuestras listas. Primero un cambio pequeño en “ListaAuditableConElementosLongitudPar”:

public class ListaAuditableConElementosLongitudPar extends ListaAuditable {
  private EstrategiaDeFiltrado filtro = new FiltrarElementosLongitudImpar();
  public ListaAuditableConElementosLongitudPar(Auditor auditor) {
    super(auditor);
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return filtro.esInsertable(elemento);
  }
}

y lo mismo en “ListaAuditableConElementosSinEspacios”:

public class ListaAuditableConElementosSinEspacios extends ListaAuditable {
  private EstrategiaDeFiltrado filtro = new FiltrarElementosConEspacios();
  public ListaAuditableConElementosSinEspacios(Auditor auditor) {
    super(auditor);
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return filtro.esInsertable(elemento);
  }
}

Mmmm, si os fijais “ListaAuditableConElementosLongitudPar” y “ListaAuditableConElementosSinEspacios” tienen exactamente el mismo código, salvo que cada una usa una implementación concreta diferente de “EstragiaDeFiltrado”. Una violación del DRY muy clara. Además ese “new” es muy, pero que muy feo ¿No sería mejor pasar el colaborador, es decir, la implementación concreta de “EstrategiaDeFiltrado”, por el constructor? Esto nos elimina la duplicación y de paso podemos borrar una clase. ¡ Me encanta cuando puedo borrar código ! ¿Y a vosotros? Ahora ambas clases desaparecen y son sustituidas por “ListaAuditableConFiltro”:

public class ListaAuditableConFiltro extends ListaAuditable {
  private EstrategiaDeFiltrado filtro;
  public ListaAuditableConFiltro(Auditor auditor, EstrategiaDeFiltrado filtro) {
    super(auditor);
    this.filtro = filtro;
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return filtro.esInsertable(elemento);
  }
}

De forma análoga, “ListaConElementosLongitudPar” y “ListaConElementosSinEspacios” acaban siendo eliminadas y sustituidas por “ListaConFiltro”. Esto no me termina de convencer. Seguimos violando DRY. Por un lado “ListaConFiltro” y “ListaAuditableConFiltro” tienen lógica claramente duplicada. Por otro lado, el método protegido “esInsertable” es exactamente igual que la interfaz “EstrategiaDeFiltrado”. El código pide a gritos ser simplificado, así que, ¿qué tal si nos llevamos todo ese código común a la superclase “ListaSencilla”? El único problema es que de “ListaSencilla” heredan también listas que no tienen filtro, ¿qué hacemos? Podemos aplicar el patrón “null object” y crear una “EstrategiaDeFiltrado” que no haga nada. Veamos ese código:

public final class NuncaFiltrar implements EstrategiaDeFiltrado {
  public NuncaFiltrar() {
    super();
  }
  @Override
  public boolean esInsertable(String elemento) {
    return true;
  }
}

Por otro lado no queremos tener que configurar el colaborador filtro con la clase “NuncaFiltrar” cada vez que queramos una lista sin filtro. Por lo tanto necesitamos que el filtro sea una dependencia opcional dentro de “ListaSencilla”. El código quedaría:

public class ListaSencilla implements Lista {
  private List datos = new ArrayList();
  private EstrategiaDeFiltrado filtro = new NuncaFiltrar();
  public ListaSencilla() {
    super();
  }
  @Override
  final public void insertar(String elemento) {
    if (!filtro.esInsertable(elemento))
      throw new ElementoRechazadoError(elemento);
    datos.add(elemento);
    procesamientoAdicionalTrasInsertarElemento(elemento);
  }
  @Override
  final public void insertarVarios(Collection elementos) {
    for (String elemento : elementos) {
      if (!filtro.esInsertable(elemento))
        throw new ElementoRechazadoError(elemento);
    }
    for (String elemento : elementos)
      insertar(elemento);
  }
  public final void configurarFiltro(EstrategiaDeFiltrado nuevoFiltro) {
    this.filtro = nuevoFiltro;
  }
  /**
   * Este método es invocado cada vez que un nuevo elemento
   * es insertado en la Lista
   *
   * @param elemento
   *        El elemento que acaba de ser insertado
   */
  protected void procesamientoAdicionalTrasInsertarElemento(String elemento)
  {
    // Intencionadamente en blanco
  }
  @Override
  public String describirContenido() {
    StringBuilder descripcion = new StringBuilder("Contenidos: ");
    for (String elemento : datos) {
      descripcion.append("'");
      descripcion.append(elemento);
      descripcion.append("' ");
    }
    return descripcion.toString();
  }
}

Obsérvese como se recibe el colaborador mediante un “setter” llamado “configurarFiltro”, y no por el constructor. También como se usa por defecto una instancia de “NuncaFiltrar” en caso de que no se configure nada. También hemos eliminado el método protegido “esInsertable”.

Finalmente hemos conseguido nuestro objetivo, hemos eliminado todas las subclases de “ListaSencilla” que creamos con el único fin de añadir lógica de filtrado. El patrón que estamos usando es el “Strategy” tal y como apareción en GoF. Podemos volver a usar el patrón “Strategy” para eliminar “ListaAuditable” y el método protegido “procesamientoAdicionalTrasInsertarElemento”. Para ello creamos una interfaz “PostProcesador”:

public interface PostProcesador {
  void postProcesar(String elemento);
}

y una implementación “Auditar”:

public class Auditar implements PostProcesador {
  private Auditor auditor;
  public Auditar(Auditor auditor) {
    super();
    this.auditor = auditor;
  }
  @Override
  public void postProcesar(String elemento) {
    auditor.elementoInsertado(elemento);
  }
}

Obsérvese que “Auditar” no es más que un patrón “Adapter”, entre la interfaz “PostProcesador” y “Auditor”. Además sospechosamente “Auditar” se parece mucho a “ListaAuditable”. Finalmente “ListaSencilla” queda:

public final class ListaSencilla implements Lista {
  private List datos = new ArrayList();
  private EstrategiaDeFiltrado filtro = new NuncaFiltrar();
  private PostProcesador postProcesador = new NoProcesar();
  public ListaSencilla() {
    super();
  }
  @Override
  public void insertar(String elemento) {
    if (!filtro.esInsertable(elemento))
      throw new ElementoRechazadoError(elemento);
    datos.add(elemento);
    postProcesador.postProcesar(elemento);
  }
  @Override
  public void insertarVarios(Collection elementos) {
    for (String elemento : elementos) {
      if (!filtro.esInsertable(elemento))
        throw new ElementoRechazadoError(elemento);
    }
    for (String elemento : elementos)
      insertar(elemento);
  }
  public void configurarFiltro(EstrategiaDeFiltrado nuevoFiltro) {
    this.filtro = nuevoFiltro;
  }
  public void configurarPostProcesador(PostProcesador nuevoPostProcesador) {
    this.postProcesador = nuevoPostProcesador;
  }
  @Override
  public String describirContenido() {
    StringBuilder descripcion = new StringBuilder("Contenidos: ");
    for (String elemento : datos) {
      descripcion.append("'");
      descripcion.append(elemento);
      descripcion.append("' ");
    }
    return descripcion.toString();
  }
}

Ahora “ListaSencilla” es “final”, sin ningún método protegido y hemos eliminado “ListaAuditable”. Hemos conseguido cubrir todos nuestros requisitos sin necesitar la “potencia” de la herencia de implementación. Además hemos simplificado los tests. Es muy sencillo hacer tests de las implementaciones de “EstrategiaDeFiltrado” y “PostProcesador”. También los tests de “ListaSencilla” pueden ser simplificados usando dobles de prueba.

Sólo nos queda una cosa, el famoso patrón “Factory”:

public final class NuevaLista {
  public NuevaLista() {
    super();
  }
  private ListaSencilla configurarAuditorEstricto(ListaSencilla lista) {
    lista.configurarPostProcesador(new Auditar(new AuditorEstricto()));
    return lista;
  }
  private ListaSencilla configurarFiltro(ListaSencilla lista,
      EstrategiaDeFiltrado filtro) {
    lista.configurarFiltro(filtro);
    return lista;
  }
  public Lista sencilla() {
    return new ListaSencilla();
  }
  public Lista auditable() {
    return configurarAuditorEstricto(new ListaSencilla());
  }
  public Lista sinElementosConEspacios() {
    return configurarFiltro(new ListaSencilla(),
        new FiltrarElementosConEspacios());
  }
  public Lista sinElementosLongitudImpar() {
    return configurarFiltro(new ListaSencilla(),
        new FiltrarElementosLongitudImpar());
  }
  public Lista auditableSinElementosConEspacios() {
    return configurarAuditorEstricto(configurarFiltro(new ListaSencilla(),
        new FiltrarElementosConEspacios()));
  }
  public Lista auditableSinElementosLongitudImpar() {
    return configurarAuditorEstricto(configurarFiltro(new ListaSencilla(),
        new FiltrarElementosLongitudImpar()));
  }
}

O si son ustedes partidarios de usar frameworks, pueden usar el framework “Spring” que da mucho juego para esto, y programarse exactamente la misma lógica que tenemos en “NuevaLista” pero en XML en vez de en JAVA (es que el XML queda más profesional). Con este patrón “Factory” podemos tener todas las combinaciones de “Lista”, “EstrategiaDeFiltrado” y “PostProcesador” que queramos, sin necesidad de volver a tocar una linea de código en ninguna de estas.

El patrón “Strategy” nos permite hacer una cosa que no podemos con “extends”, definir la combinación que queramos de “Lista”, “EstrategiaDeFiltrado” y “PostProcesador” en tiempo de ejecución.

Podríamos dejarlo aquí, pero he de comentar que “Strategy” comparte un problema con “Template Method”. Con ambos necesitamos planear de antemano que “ejes” de extensibilidad o composición vamos a tener. Esto hace que la clase “ListaSencilla” no sea tan sencilla. Estamos en cierto modo violando el principio de única responsabilidad, ya que está claro que la responsabilidad de “ListaSencilla” es simplemente gestionar la lógica de “Lista”. Además, estamos pagando una pequeña sobrecarga, al añadir lógica de llamada a filtrado y postprocesamiento en aquellos casos que realmente no la necesitamos. Cierto, es una sobrecarga muy pequeña, pero no deja de ser una mala señal. Yo lo que quiero es que “ListaSencilla” sea eso, “sencilla”. Quiero este código:

public final class ListaSencilla implements Lista {
  private List datos = new ArrayList();
  public ListaSencilla() {
    super();
  }
  @Override
  public void insertar(String elemento) {
    datos.add(elemento);
  }
  @Override
  public void insertarVarios(Collection elementos) {
    for (String elemento : elementos)
      insertar(elemento);
  }
  @Override
  public String describirContenido() {
    StringBuilder descripcion = new StringBuilder("Contenidos: ");
    for (String elemento : datos) {
      descripcion.append("'");
      descripcion.append(elemento);
      descripcion.append("' ");
    }
    return descripcion.toString();
  }
}

¡ Ah, que gusto volver a los viejos tiempos donde las listas eran listas y nada más ! Pero necesitamos filtrado y postprocesamiento, ¿qué hacemos? Recurrir a otro patrón del “GoF”, el “Decorator”. Podemos hacer una clase que admita una “EstrategiaDeFiltrado”, pero que no implemente nada de la lógica de la lista, sino que la delegue a un colaborador. O sea, la responsabilidad de esta nueva clase es simplemente orquestar a una “Lista” y a una “EstrategiaDeFiltrado”, sin que ninguna de las dos sepa nada. Creemos, para tal efecto, la clase “ListaConFiltrado”:

public final class ListaConFiltrado implements Lista {
  private Lista lista;
  private EstrategiaDeFiltrado filtro;
  public ListaConFiltrado(Lista lista, EstrategiaDeFiltrado filtro) {
    super();
    this.lista = lista;
    this.filtro = filtro;
  }
  @Override
  public void insertar(String elemento) {
    if (!filtro.esInsertable(elemento))
      throw new ElementoRechazadoError(elemento);
    lista.insertar(elemento);
  }
  @Override
  public void insertarVarios(Collection elementos) {
    for (String elemento : elementos) {
      if (!filtro.esInsertable(elemento))
        throw new ElementoRechazadoError(elemento);
    }
    lista.insertarVarios(elementos);
  }
  @Override
  public String describirContenido() {
    return lista.describirContenido();
  }
}

También podemos hacer lo mismo y crear una “ListaConPostProcesamiento”:

public final class ListaConPostProcesamiento implements Lista {
  private Lista lista;
  private PostProcesador postProcesador;
  public ListaConPostProcesamiento(Lista lista, PostProcesador postProcesador) {
    super();
    this.lista = lista;
    this.postProcesador = postProcesador;
  }
  @Override
  public void insertar(String elemento) {
    lista.insertar(elemento);
    postProcesador.postProcesar(elemento);
  }
  @Override
  public void insertarVarios(Collection elementos) {
    lista.insertarVarios(elementos);
    for (String elemento : elementos)
      postProcesador.postProcesar(elemento);
  }
  @Override
  public String describirContenido() {
    return lista.describirContenido();
  }
}

¿Necesitaremos una clase “ListaConFiltradoYPostProcesamiento”? No, tal objeto se puede crear componiendo una “ListaConFiltrado” con una “ListaConPostProcesamiento”, pasando la última como argumento al constructor de la primera. De hecho ahora podemos eliminar “NuncaFiltrar” y “NoProcesar” ya que no las usamos. Además volvemos a simplificar los tests de “ListaSencilla”, ¡ ya no necesitamos dobles de prueba ! Por otro lado los tests de “ListaConFiltrado” y “ListaConPostProcesamiento” son muy sencillos, basta usar unos dobles de prueba para comprobar que las llamadas se delegan en el orden oportuno, y que no se llama ni a “insertar” ni a “insertarVarios” si el filtro nos devuelve “false”.

Si os fijais estoy usando varios patrones que no usan herencia de implementación, sino una filosofía de diseño general llamada “composición y delegación“. Esta filosofía consiste en tener objetos sencillos, y bien encapsulados, que puedan ser combinados mediante composición, para obtener nueva funcionalidad. Tanto “Strategy”, como “Decorator” usan esta técnica.

Siguiendo con el ejemplo, todos estos cambios afectan a nuestra factoría “NuevaLista”, que ahora queda:

public final class NuevaLista {
  public NuevaLista() {
    super();
  }
  public Lista sencilla() {
    return new ListaSencilla();
  }
  public Lista auditable() {
    return new ListaConPostProcesamiento(sencilla(), new Auditar(
        new AuditorEstricto()));
  }
  public Lista sinElementosConEspacios() {
    return new ListaConFiltrado(sencilla(), new FiltrarElementosConEspacios());
  }
  public Lista sinElementosLongitudImpar() {
    return new ListaConFiltrado(sencilla(), new FiltrarElementosLongitudImpar());
  }
  public Lista auditableSinElementosConEspacios() {
    return new ListaConFiltrado(auditable(), new FiltrarElementosConEspacios());
  }
  public Lista auditableSinElementosLongitudImpar() {
    return new ListaConFiltrado(auditable(),
        new FiltrarElementosLongitudImpar());
  }
}

Ahora ese “Factory” es más sencillo y elegante que antes al haber eliminado los “setter”. La clase “NuevaLista” simplemente compone o combina las distintas implementaciones de lista. Desgraciadamente cada vez que necesitemos una nueva combinación tenemos que tocar “NuevaLista” (o el XML de “Spring”). Para solventar ese problema podríamos aprovechar la potencia de “componer y delegar” y hacernos un mini DSL interno. Veamos cómo:

public final class FabricaDeListas {
  private List<EstrategiaDeFiltrado> filtros = new ArrayList();
  private List<PostProcesador> postProcesadores = new ArrayList();
  public FabricaDeListas() {
    super();
  }
  public FabricaDeListas(List filtros,
      List postProcesadores) {
    this();
    this.filtros.addAll(filtros);
    this.postProcesadores.addAll(postProcesadores);
  }
  public Lista fabricar() {
    Lista resultado = new ListaSencilla();
    for (PostProcesador procesador : postProcesadores)
      resultado = new ListaConPostProcesamiento(resultado, procesador);
    for (EstrategiaDeFiltrado filtro : filtros)
      resultado = new ListaConFiltrado(resultado, filtro);
    return resultado;
  }
  public FabricaDeListas conPostProcesador(PostProcesador procesador) {
    FabricaDeListas fabrica = new FabricaDeListas(this.filtros,
        this.postProcesadores);
    fabrica.postProcesadores.add(procesador);
    return fabrica;
  }
  public FabricaDeListas conAuditor(Auditor auditor) {
    return conPostProcesador(new Auditar(auditor));
  }
  public FabricaDeListas conAuditoriaEstricta() {
    return conAuditor(new AuditorEstricto());
  }
  public FabricaDeListas conFiltro(EstrategiaDeFiltrado filtro) {
    FabricaDeListas fabrica = new FabricaDeListas(this.filtros,
        this.postProcesadores);
    fabrica.filtros.add(filtro);
    return fabrica;
  }
  public FabricaDeListas sinElementosLongitudImpar() {
    return conFiltro(new FiltrarElementosLongitudImpar());
  }
  public FabricaDeListas sinElementosConEspacios() {
    return conFiltro(new FiltrarElementosConEspacios());
  }
}

Simplemente usamos un patrón “Value Object” mezclado con un “Builder”. La ventaja de este enfoque es que sea cual sea la combinación que quiera la puedo obtener fácilmente con “FabricaDeListas”, sin tener que tocar una sola linea de código dentro de “FabricaDeListas”. Puedo hacer cosas como:

Lista auditadaSinElementosConEspacios = fabrica
                                           .sinElementosConEspacios()
                                           .conAuditoriaEstricta()
                                           .fabricar();

Mucho más legible, ¿no?

Tras este largo camino, ¿alguien considera que el código resultante es ilegible, poco mantenible o poco potente? ¿Para que nos sirvió el “extends” si no fue sólo para darnos dolores de cabeza? Hemos aprendido una valiosa lección, cualquier diseño basado en “extends” y “Template Method” puede ser refactorizado en un “Strategy”, y éste a su vez en un “Decorator”. Por lo tanto, ¿para que perder el tiempo usando “extends” si puedo hacer un “Strategy” o un “Decorator”? Al fin y al cabo el diseño basado en “composición y delegación” es más seguro y más potente.

Sin embargo aquí entra en juego las herramientas que te da el lenguaje. Una pista nos la puede dar la clase “Auditar”, donde estamos creando una clase para simplemente adaptar la interfaz “PostProcesador” a “Auditor”, pero realmente no hay ninguna lógica ahí. A mi me parece que esa clase es un buen desperdicio de código, pero la verdad es que no se puede hacer mucho más en JAVA. Otro ejemplo es el método “describirContenido” en “ListaConFiltrado” y “ListaConPostProcesamiento”. Ese método no hace nada, sólo delega la llamada sin añadir lógica, ¡ qué desperdicio de líneas de código ! Tampoco podemos hacer mucho más en leguaje JAVA. Afortunadamente tenemos IDEs como “Eclipse”, “NetBeans” o “IntelliJ” que nos proporcionan el poderoso wizard “Generate Delegate Methods…”, y no las tenemos que escribir.

Cuando veo este tipo de detalles es cuando pienso que JAVA no está envejeciendo muy bien. Gracias señores estandarizadores de JAVA, perdieron ustedes una gran oportunidad de mejorar el lenguaje cuando lanzaron JAVA 5. En vez de parir ese aborto de “Map<? extends Enumerable, List<? extends X>>”, debían haber mejorado el lenguaje para añadir una forma sencilla de aplicar la técnica de “composición y delegación”, ¿tal vez una palabra clave “delegate”? ¿O soporte para funciones como primer ciudadano? No, ¿para qué? Los genéricos son mucho más “molones” y para reutilizar código ya tenemos el “extends”. Todo esto es mucho más simple de hacer en JavaScript, Groovy o Ruby. A ver si en JAVA 7 o JAVA 8 se ponen las pilas.

¡ Gracias por aguantar hasta el final de este post tan largo !

Read Full Post »


Hola de nuevo, como vimos en el anterior post la herencia de implementación puede ser un verdadero problema. Sí, es verdad, existen técnicas para diseñar nuestras clases para que puedan ser reutilizadas mediante herencia. Notad la cosa tan fea que acabo de decir. Si quiero que mi clase sea reutilizada, tengo que diseñarla de antemano, es decir, no importa para nada la interfaz de la clase, ni los tests que tenga sobre esta, ni la especificación del contrato. Si quiero reutilizar utilizando “extends” tengo que saber como está implementada la clase internamente, incluyendo todas las cosas “private” que tenga por dentro. A algunos les parecerá de lo más normal, a mi me da que pensar, ¿y entonces, para que leches sirve la encapsulación?

En cualquier caso voy a profundizar un poco en como hacer las cosas “bien” con la herencia de implementación, para ver las limitaciones de la técnica, y para ver si me quito el San Benito de programador petardo.

Tras reflexionar sobre el problema de mantenibilidad que tienen con la clase “Lista”, el equipo decide contratar a un consultor externo, y tras convencer a la mesa de compras de que paguen la estratosférica tarifa de 45 euros/hora, consiguen contratar a uno de los más reputados expertos locales. Haciendo honor a sus honorarios, el experto diagnostica y resuelve el problema en un santiamén (lástima que cobre jornada o fracción). “Pero señor@s”, dice el experto, “¿es que nunca habéis leído el GoF?”, continua, “aquí os pongo un patrón Template Method que os vais a chupar los dedos”. Ni corto ni perezoso modifica el código, y ListaSencilla le queda así.

public class ListaSencilla implements Lista {
  private List datos = new ArrayList();
  public ListaSencilla() {
    super();
  }
  @Override
  final public void insertar(String elemento) {
    datos.add(elemento);
    procesamientoAdicionalTrasInsertarElemento(elemento);
  }
  @Override
  final public void insertarVarios(Collection elementos) {
    for (String elemento : elementos)
      insertar(elemento);
  }
  /**
   * Este método es invocado cada vez que un nuevo elemento
   * es insertado en la Lista
   * @param elemento El elemento que acaba de ser insertado
   */
  protected void procesamientoAdicionalTrasInsertarElemento(String elemento)
  {
    // Intencionadamente en blanco, es un "hook" para sobreescribir
  }
  @Override
  public String describirContenido() {
    StringBuilder descripcion = new StringBuilder("Contenidos: ");
    for (String elemento : datos) {
      descripcion.append("'");
      descripcion.append(elemento);
      descripcion.append("' ");
    }
    return descripcion.toString();
  }
}

Nótese el uso de la palabra clave “final”. Esta palabra clave se puede poner a nivel de método, o mejor, a nivel de clase, indicando que no se puede sobrescribir el método o hacer extends de la clase. La palabra clave “final” es un gran invento de JAVA, pero claro,  hay que leer entre líneas: un lenguaje que tiene herencia de implementación, pero que además tiene una palabra clave que prohíbe la herencia, muy sospechoso. En realidad los diseñadores de JAVA sabían muy bien que la reutilización de código mediante herencia de implementación es peligrosa. Conscientes de ellos, añadieron “final”, para dar la capacidad al desarrollador de marcar una clase o método con “¡Peligro, no heredar o sobrescribir!”, en el caso de que no hubieran diseñado de antemano la clase para herencia. Desgraciadamente no he visto que mucha gente use “final”.

Siguiendo con la historia, al usar “final” y “Template Method” en conjunción con el método protegido “procesamientoAdicionalTrasInsertarElemento”, se desactiva cualquier problema. Por un lado no podemos sobrescribir los métodos peligrosos, ya que son “final”. Por otro lado el implementador de la clase diseña su código de tal forma que si quiero reutilizar la clase, no lo pueda hacer de cualquier forma, sino sólo sobrescribiendo el método protegido a tal fin. Con este cambio, la clase “ListaAuditable” queda como sigue:

public class ListaAuditable extends ListaSencilla {
  private Auditor auditor;
  public ListaAuditable(Auditor auditor) {
    super();
    this.auditor = auditor;
  }
  @Override
  protected void procesamientoAdicionalTrasInsertarElemento(String elemento)
  {
    auditor.elementoInsertado(elemento);
  }
}

Da la impresión de ser un código bastante elegante. Desde luego ha merecido la pena pagar la exorbitante tarifa del consultor externo. Sólo hay un problema pequeñito, el test de “ListaSencilla” se ha complicado. Claro, ahora no sólo hay que testear el contrato “Lista” en “ListaSencilla”, que es el que define como se usa la clase por parte de los consumidores de esta. Ahora hay otro contrato, representado por el método protegido, entre “ListaSencilla” y todas las clases que la extiendan. Este contrato es “en negro”, ya que no es público, sino “protegido”. Necesitamos un test que pruebe que el método protegido es llamado en los momentos adecuados y con los parámetros adecuados. Pero este contrato no está en una interfaz de un colaborador, sino en un método “protected”, ¿cómo hacemos el test? Afortunadamente con un poco de programación “clever” se puede terminar haciendo testing de esto. Lo dejo como ejercicio al lector.

Otro pequeñito problema es que alguien siga pensando que no tiene por que entender la implementación de la superclase, que le vale sólo entendiendo el contrato público (pobrecito), y al heredar haga cosas como esta:

public class ListaAuditablePetarda extends ListaSencilla {
  private Auditor auditor;
  public ListaAuditablePetarda(Auditor auditor) {
    super();
    this.auditor = auditor;
  }
  @Override
  protected void procesamientoAdicionalTrasInsertarElemento(String elemento)
  {
    super.insertar(elemento); // Ouch !!!
    auditor.elementoInsertado(elemento);
  }
}

¡ Ay, que cosa más fea ! ¿Es que nadie se lee el Javadoc? Obviamente nadie debería usar super para llamar a un método peligroso (final), sólo sobrescribir métodos protegidos o métodos públicos no peligrosos (no final). Bueno, a parte de estos problemitas subsanables con un poco de “hacking”, y despedir al inútil que cometió la anterior tropelía, parece que la técnica funciona.

Pasa el tiempo y aparece otro problema con este enfoque. ¿Qué ocurre si queremos hacer la extensión de una clase de una forma no prevista de antemano? No puedes. Tienes que volver a modificar la clase padre para añadir extensibilidad en el nuevo “eje” que te haga falta. Por supuesto en cualquier desarrollo serio, ha habido una extensa e intensiva fase de análisis funcional y diseño técnico, y esas cosas no pueden pasar, ya que se han cubierto todos los posibles cambios. Desgraciadamente nuestros amigos no son tan profesionales y usan una cosa llamada “agile” que les impide hacer un buen análisis y diseño “up-front” como dios manda, y no tienen muy claro las necesidades de diseño futuro de su sistema.

Nuestros sufridos desarrolladores se encuentran con un nuevo requisito, ahora resulta que algunas especializaciones de “ListaSencilla” pueden rechazar un elemento, y negarse a insertarlo si cumple alguna característica concreta. Desgraciadamente en las clases hijas de “ListaSencilla” no pueden añadir esta funcionalidad, deben abrir su implementación, y siguiendo “Template Method” añadir un punto de extensión más.

public class ListaSencilla implements Lista {
  private List datos = new ArrayList();
  public ListaSencilla() {
    super();
  }
  @Override
  final public void insertar(String elemento) {
    if (!esInsertable(elemento))
      throw new ElementoRechazadoError(elemento);
    datos.add(elemento);
    procesamientoAdicionalTrasInsertarElemento(elemento);
  }
  @Override
  final public void insertarVarios(Collection elementos) {
    for (String elemento : elementos) {
      if (!esInsertable(elemento))
        throw new ElementoRechazadoError(elemento);
    }
    for (String elemento : elementos)
      insertar(elemento);
  }
  /**
   * Este método es invocado para pedir permiso sobre si un elemento
   * puede ser insertado o no
   *
   * @param elemento
   *        El elemento que queremos insertar
   * @return si se puede insertar o no
   */
  protected boolean esInsertable(String elemento) {
    return true;
  }
  /**
   * Este método es invocado cada vez que un nuevo elemento
   * es insertado en la Lista
   *
   * @param elemento
   *        El elemento que acaba de ser insertado
   */
  protected void procesamientoAdicionalTrasInsertarElemento(String elemento)
  {
    // Intencionadamente en blanco
  }
  @Override
  public String describirContenido() {
    StringBuilder descripcion = new StringBuilder("Contenidos: ");
    for (String elemento : datos) {
      descripcion.append("'");
      descripcion.append(elemento);
      descripcion.append("' ");
    }
    return descripcion.toString();
  }
}

Bueno, no duele tanto, al fin y al cabo el código parece razonablemente limpio y “ListaAuditable” ni se ha enterado. Ahora implementamos el requisito, realmente ocurre que pueden existir tres tipos de listas auditables, aquellas que no admiten elementos que contengan espacios y aquellas que sólo admiten elementos de longitud par. Bueno, a implementar. La cosa queda como sigue:

public class ListaAuditableConElementosSinEspacios extends ListaAuditable {
  public ListaAuditableConElementosSinEspacios(Auditor auditor) {
    super(auditor);
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return !elemento.contains(" ");
  }
}

Y también:

public class ListaAuditableConElementosLongitudPar extends ListaAuditable {
  public ListaAuditableConElementosLongitudPar(Auditor auditor) {
    super(auditor);
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return elemento.length() % 2 == 0;
  }
}

Ok, no esta nada mal, casi podemos cantar victoria, ¡además hemos reutilizado “ListaAuditable”!

Pasa el tiempo, y con él llegan cambios, e inevitablemente nos llega otro requisito. Resulta que las listas que no son auditables también pueden existir en la forma de listas que no admiten elementos con espacios, y las que sólo admiten elementos de longitud par ¡Qué dura es la vida del desarrollador! De nuevo al código, queda lo que sigue:

public class ListaConElementosSinEspacios extends ListaSencilla {
  public ListaConElementosSinEspacios() {
    super();
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return !elemento.contains(" ");
  }
}

Y también:

public class ListaConElementosLongitudPar extends ListaSencilla {
  public ListaConElementosLongitudPar() {
    super();
  }
  @Override
  protected boolean esInsertable(String elemento) {
    return elemento.length() % 2 == 0;
  }
}

¡Han aparecido otras dos clases! ¡Ya tenemos 6 implementaciones de “Lista”! Bueno, al menos son cortitas y sencillas, y estamos reutilizando un montón. El problema es que estamos a empezar a detectar un tufillo a violación del DRY. De hecho es la misma implementación de antes pero heredando de “ListaSencilla” en vez de “ListaAuditable” ¿Quién dijo que el copy&paste era malo? Quizás si en vez de usar “extends” usáramos otra cosa… nada, todo el mundo sabe que la herencia es “esencial” a la OO, su “killer feature”, mejor seguimos con el “Template Method”, y no liamos más la cosa.

Seis meses más tarde tenemos 60 clases “Lista”. Resultó que salieron cinco formas más de filtrar los elementos de las listas, y encima además de listas “auditables” y “sencillas”, tenemos listas “lazy”, listas “persistentes”, etc. En fin, que necesitamos una clase por cada combinación posible. La verdad es que esto ya no parece tan limpio.

Una última reflexión. Para el que sea propenso a pensar en cosas inútiles, aquí lanzo una pregunta, ¿cómo es mejor implementar ListaAuditableConElementosLongitudPar? ¿Heredando de ListaConElementosLongitudPar o heredando de ListaAuditable? ¿Da igual? Y en caso de que sea igual, ¿no parece un accidente histórico el decidir implementarla de una forma y no de otra, y no una decisión de diseño guiada por criterios de ingeniería del software? Inquietante, al menos para mi.

Reconozco que este es un ejemplo un poco “cogido por pinzas”, pero no me negaréis que ilustra un problema real, que tal vez muchos estáis reconociendo de haberlo sufrido. Esta explosión combinatoria de clases es algo que se conoce también desde antiguo, y está ligado al hecho de que tenemos herencia de implementación simple. Si alguno está pensando en solucionarlo con herencia de implementación múltiple, nada vosotros mismos. Si no habéis tenido bastante con la herencia simple, ahora si que os podéis meter en un buen lío. Está claro que el verdadero problema reside en usar “extends” como mecanismo de  reutilización de código.

Resumiendo:

  • Puedo reutilizar código con “extends”, pero tengo que diseñar de antemano para ello.
  • Al diseñar de antemano, sólo puedo reutilizar aquello que está pensado para serlo, y no cualquier característica pública de la interfaz de la clase. Si necesito reutilizar mediante “extends” alguna característica de la clase que no esté diseñada para ello, pues o me aguanto, o no uso “extends” o simplemente abro la clase padre y la modifico (muy SOLID no es, no).
  • El que reutilice con “extends” una clase debe conocer si ésta está diseñada para ello, y por lo tanto conocer la implementación interna de la clase padre.
  • La forma más común (no la única), es usar un patrón “Template Method” de GoF, pero esto dificulta el testing, ¿cómo pruebo los métodos protected?
  • Algún programador petardo, que no se haya leído nuestro excelente Javadoc de los métodos “protected”, puede acabar invocando al “super” y terminar con errores en las nuevas clases, errores que a veces son sutiles de detectar.
  • Esta forma de reutilizar código está sujeta además a la explosión combinatoria de clases.

Algunos argumentaron en el post anterior que “extends” no sirve como mecanismo de reutilización de código, sino como mecanismo de especialización. O sea, para especializar clases pero sin reutilizar código. Pues vaya tela, ¡yo pensaba que lo que mantenían relaciones de especialización eran las interfaces!. Y que alguien me diga, ¡ para que sirve la herencia de implementación si no es para reutilizar código ! ¡Para que quiero especializar una clase sin reutilizar su código! ¡ Quiero un ejemplo concreto !

¿Existe alguna forma mejor de reutilizar código? La respuesta es sí, pero viene con un precio a pagar. A algunos, como a mi, este precio le parece pequeño, pero a otros les parece enorme. Lo veremos en el siguiente post.

Read Full Post »


Hola a todos, este post me lo venía pidiendo el cuerpo desde hace tiempo. Es común oírme decir que la herencia de implementación (“extends” para los javeros) es una mierda, y cada vez que lo digo veo caras de estupefacción e incluso recibo alguna que otra crítica. La verdad es que no lo entiendo bien ya que esto se sabe desde hace bastante tiempo, es más hasta tiene un nombre: “problema de la clase base frágil”, aunque yo prefiero decir que “extends es una mierda”. Incluso, algunos dicen que James Goslin, el padre de JAVA, reconoció que incluir “extends” en el lenguaje fue un gran error. Yo considero que “extends” no tiene nada que ver con la OO (al menos no con el concepto original), sino que se incluyó porque para algo tenían que servir las clases, y se pensó que se podrían usar para reutilizar código mediante la herencia de implementación.

Vamos al lío, os voy a contar una historia. Suponed la siguiente interfaz:

public interface Lista {
	void insertar(String elemento);
	void insertarVarios(Collection elementos);
	String describirContenido();
}

Se trata de una interfaz sencilla que representa una lista, con dos métodos para añadir contenido. Veamos ahora una implementación de tal interfaz:

public class ListaSencilla implements Lista {
	private List datos = new ArrayList();

	public ListaSencilla() {
		super();
	}

	@Override
	public void insertar(String elemento) {
		datos.add(elemento);
	}

	@Override
	public void insertarVarios(Collection elementos) {
		for (String elemento : elementos)
			this.insertar(elemento);
	}

	@Override
	public String describirContenido() {
		StringBuilder descripcion = new StringBuilder("Contenidos: ");
		for (String elemento : datos) {
			descripcion.append("'");
			descripcion.append(elemento);
			descripcion.append("' ");
		}
		return descripcion.toString();
	}
}

¿Sencillo verdad? Es una implementación muy directa y no tiene mayor misterio. Ahora supongamos que a otro miembro del equipo, que no escribió la clase “ListaSencilla”, le encargan desarrollar una implementación de “Lista” que cada vez que se añada un elemento a ésta, informe a un objeto auditor de tal suceso. Como nuestro desarrollador está enfocado en el objetivo de esta nueva clase, que es informar al auditor, y no le interesa volver a reimplementar y duplicar la lógica de inserción de datos, decide reutilizar código. ¿Qué mejor manera de utilizar el venerable “extends”? Al fin y al cabo, la herencia de implementación es la verdadera ventaja de la OO y su mecanismo más potente para reutilizar código. Mirando la especificación de “Lista” y conociendo que ya hay una implementación “ListaSencilla” disponible, le sale esto:

public class ListaAuditable extends ListaSencilla {
	private Auditor auditor;

	public ListaAuditable(Auditor auditor) {
		super();
		this.auditor = auditor;
	}

	@Override
	public void insertar(String elemento) {
		super.insertar(elemento);
		this.auditor.elementoInsertado(elemento);
	}

	@Override
	public void insertarVarios(Collection elementos) {
		super.insertarVarios(elementos);
		for (String elemento : elementos)
			this.auditor.elementoInsertado(elemento);
	}
}

El desarrollador ni se molesta en probarlo, obviamente va a funcionar, él ha respetado el contrato de la interfaz y está usando a los amiguetes “extends” y “super”. Más tarde le llega un bug, ¡resulta que el auditor es notificado por cada elemento por duplicado! Desconcertado nuestro programador recurre a esa poderosa arma de la ingeniería del software, el depurador, y consigue aclarar el misterio. La implementación del método “insertarVarios” en la superclase llama internamente a “insertar”, que como está sobreescrito en la clase hija también notifica al auditor, pero claro, “insertarVarios” de la clase hija también notifica al auditor por su cuenta, y de ahí la doble notificación al auditor. Mas claro agua, y si no lo habéis entendido, ¡depurad!

El desarrollador aprende la valiosa lección de que no sólo debe saber el contrato del código que quiere reutilizar, sino que debe analizar con detalle como está diseñada internamente la clase a reutilizar. Ya sabía él que todo esto del diseño por contrato y las interfaces no eran más que paparruchas para vender libros y cursos. Tras pensar detenidamente cambia el código y le queda:

public class ListaAuditable extends ListaSencilla {
	private Auditor auditor;

	public ListaAuditable(Auditor auditor) {
		super();
		this.auditor = auditor;
	}

	@Override
	public void insertar(String elemento) {
		super.insertar(elemento);
		this.auditor.elementoInsertado(elemento);
	}
}

Genial, bug resuelto de la mejor manera posible: borrando código. Más contento que unas castañuelas, el desarrollador se va a casa con la satisfacción del buen trabajo cumplido.

Pasa el tiempo, y un desarrollador senior, revisando la clase “ListaSencilla”, e ignorante de que esta clase tiene más descendientes que Gengis Khan, decide optimizar el método “insertarVarios”. Decide ahorrarse el bucle, y que leches, reutilizar código de la clase “ArrayList” que para eso viene en la JDK, estos desarrolladores junior sufren del síndrome NIH… La nueva versión:

public class ListaSencilla implements Lista {
	private List datos = new ArrayList();

	public ListaSencilla() {
		super();
	}

	@Override
	public void insertar(String elemento) {
		datos.add(elemento);
	}

	@Override
	public void insertarVarios(Collection elementos) {
		datos.addAll(elementos);
	}

	@Override
	public String describirContenido() {
		StringBuilder descripcion = new StringBuilder("Contenidos: ");
		for (String elemento : datos) {
			descripcion.append("'");
			descripcion.append(elemento);
			descripcion.append("' ");
		}
		return descripcion.toString();
	}
}

¡ Qué sencillez ! ¡ Qué elegancia ! No por nada es un “lead developer” que se conoce la JDK al dedillo. Otro que se va para casa inconsciente de la que acaba de armar. ¿Por qué debería preocuparse? Al fin y al cabo el contrato de la interfaz “Lista” se cumple, y para demostrarlo tiene un test automatizado de hermoso color verde. Mientras tanto, la “ListaAuditable” y posiblemente muchas más subclases de “ListaSencilla” comienzan a fallar sin previo aviso.

Al día siguiente, el “lead developer” decide arreglar el problema, mira a ver el código de “ListaAuditable” y lo solventa en menos que canta un gallo. El código queda así:

public class ListaAuditable extends ListaSencilla {
    private Auditor auditor;

    public ListaAuditable(Auditor auditor) {
        super();
        this.auditor = auditor;
    }

    @Override
    public void insertar(String elemento) {
        super.insertar(elemento);
        this.auditor.elementoInsertado(elemento);
    }

    @Override
    public void insertarVarios(Collection elementos) {
        super.insertarVarios(elementos);
        for (String elemento : elementos)
            this.auditor.elementoInsertado(elemento);
    }
}

¿Os resulta familiar ;-) ?

“Tonto desarrollador junior”, piensa el lead developer, “se nota que en vez de fijarse en la interfaz y los tests, se ha mirado la implementación de la superclase, y por ahorrarse un método la que liado…”. En fin, muy triste :-(

Algunos de vosotros pensará que he hecho trampas, que no he usado “bien” la herencia de implementación y que soy un petardo de programador. En el siguiente post explicaré como hacerlo correctamente. Pero ésto no es lo importante, lo que quiero mostrar es que tenemos un mecanismo de reutilización de código, la herencia de implementación, que es más peligrosa que una caja de bombas. Yo prefiero no usar un mecanismo con el que tengo que andarme con pies de plomo, aunque me haya leído y entendido el contrato del código que quiera reutilizar. La herencia de implementación no es segura porque rompe la encapsulación, y necesito saber cómo está implementada la clase padre para poder heredar de ella de forma segura. Todo esto se produce porque mezcla el estado e implementación privada de la superclase con la de la clase hija en una única instancia, en vez de mantenerlas separadas. ¿No sería mejor tener un mecanismo de reutilización de código que siempre fuera seguro? Sí, claro que existe, y lo veremos más adelante.

Read Full Post »

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 42 seguidores