Contenido

Constructores simples

Esta semana me he dado cuenta de la diferencia existente entre un constructor feo y uno bonito. Es curioso lo fácil que es hacerlo bien y lo poquito que cuesta, así que voy a compartir mis apreciaciones con vosotros, a ver qué opináis.

Durante mi carrera profesional he hecho muchas cosas feas, así que no me echéis en cara si encontráis que he hecho algo diferente de lo que diga :D ¡Esto consiste en ir mejorando día a día!

Actualización 2012/01/24: Por petición de YeBeNeS, añado ejemplos en java.

Patterns

Lanzando excepciones

Tener un constructor que lanza excepciones es algo que ya resulta algo feo. Un constructor debería preparar el objeto para ser utilizado, no realizar una tarea. Dado que no realiza tareas, no debería lanzar ninguna excepción.

Cuando digo que no lanza excepciones no significa que las capture: digo que no las lanza porque no lo necesita. Las actividades que haga serán tan simples que no pueden fallar: inicializaciones y punto. Nada de operaciones. Las cosas complejas las dejamos para los métodos.

No hay nada más feo que un constructor que lanza excepciones. Es lo último que te esperas y es lo último que deseas que otros se encuentren.

Cuando el constructor tiene excepciones, éste sería el código mínimo para instanciarlo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Example {
   public Example() throws exception {
   }
}
// [...]

try {
    Example example = new Example();
} catch (Exception e) {
}

Null pointer exception

Si un constructor no realiza operaciones, no puede darse el caso de acceder a un puntero inválido. El constructor sólo debería hacer asignaciones, nada más.

Constructores con parámetros

Hay ocasiones en las que un objeto requiere de un parámetro para poder funcionar. En estos casos, está bien requerirlo en el constructor.

Cuando el número de parámetros es muy alto (es decir, DOS), entonces deberíamos plantearnos hacerlo de otra manera. Si el constructor requiere tantos parámetros, es posible que esté violando el principio de única responsabilidad .

Otra costumbre que tomé y que he visto es la de crear un constructor con N parámetros, de manera que permita inicializar cada uno de sus atributos privados. La experiencia me ha demostrado que es una costumbre bastante mala… en casi todos los casos.

En el caso de Java, es una mala costumbre lo mires como lo mires. ¿Cuál es el orden de los parámetros? Si tengo 3 parámetros… ¿Tengo que hacer las 3 combinaciones de parámetros únicos, al menos 2 de 2 argumentos y otro de 3? ¿Y si, de pronto, necesito un cuarto atributo privado? ¿Cuántas combinaciones tengo que hacer?

En otros lenguajes, como Python, en el que los argumentos son nombrados, todo lo dicho no le afecta. Sin embargo, en este caso tendremos otro problema: ¿Y si ya no necesito uno de los argumentos o si necesito cambiarle el nombre?

A ver… ante el código siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Example {
  private int value1;
  private int value2;
  private String juntaLaTrocola;
  private String gamusino;

  public Example() {}
  public Example(int value1){}
  public Example(int a, int b) {}
  public Example(String juntaLaTrocola) {}
  public Example(String gamusino, String juntaLaTrocola() {}
}

Se me ocurren muchas preguntas:

  • En el tercer constructor, ¿va primero el value1 o el value2?
  • ¿Por qué no puedo construir un objeto sólo con “gamusino”?
  • ¿Por qué no puedo combinar valores numéricos y cadenas? ¿Es porque el programador se cansó (como en este caso)? ¿Es porque no debo? ¿Es porque se añadieron después?

Además, si heredas de la clase, ¡¡¡estás obligado a sobreescribir todos estos métodos!!!

¿Ejemplos? JDialog tiene 16 constructores distintos.

Constructores fatigados

No hace mucho que yo mismo implementé un constructor que parseaba un archivo XML. Me pareció algo horrible, pero no encontraba otra manera mejor de hacerlo. ¿Alguien le ve lógica a esto? Yo, ahora, no.

Si tenemos un constructor que hace tanto trabajo, ¿cómo podemos mejorar nuestro código? No podemos usar el patrón un método a una clase, porque tendremos que el constructor de la nueva clase hará, de nuevo, todo el trabajo. En caso contrario, ¿por qué tenemos dos clases? bastaría con sustituir la primera por la segunda.

No es algo tan raro de encontrar: la propia API de Java tiene métodos que lanzan excepciones o realizan mucho trabajo (NOTA: buscando ejemplos no he encontrado ninguno que lance excepciones, pero sé que los he visto; ¿habrá sido en clases de terceros?):

1
2
RMIConnectorServer(JMXServiceURL url, Map<String,?> environment, RMIServerImpl rmiServerImpl, MBeanServer mbeanServer);
JDialog(Window owner, String title, Dialog.ModalityType modalityType, GraphicsConfiguration gc);

La solución: Fábricas

En ocasiones necesito dos constructores porque hay distintas maneras de usar el objeto. Veamos un ejemplo: Tengo una clase que se conecta a base de datos y que se puede utilizar de dos maneras diferentes: Con una base de datos real o con una base de datos en memoria.

Aquí tengo un problema. Uno de los constructores necesitará un argumento y el otro ninguno. No es lógico utilizar otro sistema para construir el objeto… ¿O sí?

Tras haberlo hecho así numerosas veces, he descubierto que hay otra manera mucho más chula: Utilizando un método que me fabrique el objeto. De esta manera puedo diferenciar entre la forma de construirlo de una manera y la otra. Es decir: tendré el método createInMemory y createInFile. Opcionalmente podré transformar el constructor en privado para evitar su uso de forma indebida (aunque no soy muy partidario de esto).

Con esta simple transformación consigo que el constructor no lance excepciones y, además, tengo una manera elegante de ir construyendo el objeto con más de una instrucción, mucho más verboso cuando tenga que cambiarlo.

Otra ventaja: Si necesito añadir nuevos “constructores”, como por ejemplo una base de datos remota, bastará con ofrecer más fábricas: createRemote.

Un ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class DataAccess {
  public DataAccess() {}
  public static DataAccess createInMemory() throws SQLException{
    DataAccess result = new DataAccess();
    result.setConnectionString("in memory");
    result.buildDatabase();
    return result;
  }
  public static DataAccess createMySql() throws SQLException{
    DataAccess result = new DataAccess();
    result.setConnectionString("mysql");
    if (!result.hasDatabase())
        result.buildDatabase();
    return result;
  }
}

Fábricas abstractas

Considero de suma importancia hacer nuestro código de manera que pueda crecer, sin limitarlo desde su creación. El uso de estas fábricas permite que en el futuro puedan transformarse fácilmente en Fábricas Abstractas, dando mayor funcionalidad con unos cambios muy pequeños.

Pruebas

Cuando se utiliza un constructor complejo, la realización de pruebas se ve obstaculizada. En cambio, cuando el constructor es simple y lo que se complican son las operaciones, resulta más sencillo de probar, ya que puedes construir el objeto en el setUp con la completa seguridad de que no va a fallar. Sin embargo, cuando se utilizan constructores complejos, no hay manera de probarlo.

Si una operación (un método) lanza una excepción, la operación te está avisando de algo. Si es el constructor el que la lanza, no puedes estar seguro de qué operación se estaba realizando en ese momento. Esto dificulta terriblemente las pruebas y complica cualquier intento de mockear el objeto.

Conclusión

Ya que nuestras clases tienden a ser complejas, no ensuciemos el código desde el principio y tratemos de usar constructores sencillos. Cuando es el constructor el que hace el trabajo, ¿qué les queda a los métodos? Deleguemos este trabajo.