Telescoping constructor anti-pattern & Builder pattern

Cuando nuestros objetos crecen, no sólo los métodos equals() y hashCode() pueden volverse inmanejables, los constructores también pueden volverse un arma de doble filo. Instanciar un objeto con tres o cuatro parámetros es sencillo mediante su correspondiente constructor, en cambio, con objetos con un número mayor de propiedades la misma solución se vuelve poco práctica.

Vamos a ver un ejemplo práctico, imaginar que decidimos crear nuestra propia clase para manejar fechas. Nuestra clase, a la que llamaremos ‘Date’, contará con siete propiedades tal que así:


public class Date {

	private int year;

	private int month;

	private int day;

	private int hour;

	private int minute;

	private int second;

	private int millisecond;

}

¿Cómo queremos que los objetos de nuestra clase ‘Date’ sean creados?

Algo habitual podría ser el crear constructores para los casos más habituales, algo de este estilo:


	// Creamos un constructor para las combinaciones
	// más habituales
	public Date(int year, int month, int day) {
		this(year, month, day, 0, 0, 0, 0);
	}

	public Date(int year, int month, int day, int hour) {
		this(year, month, day, hour, 0, 0, 0);
	}

	public Date(int year, int month, int day, int hour,
			int minute) {
		this(year, month, day, hour, minute, 0, 0);
	}

	public Date(int year, int month, int day, int hour,
			int minute, int second) {
		this(year, month, day, hour, minute, second, 0);
	}

	public Date(int year, int month, int day, int hour,
			int minute, int second,int millisecond) {
		super();
		this.year = year;
		this.month = month;
		this.day = day;
		this.hour = hour;
		this.minute = minute;
		this.second = second;
		this.millisecond = millisecond;
	}

De esta manera tendríamos cubiertos los casos más típicos para esta clase y a la hora de instanciar una clase en nuestro IDE nos aparecería algo así:

Date01

Por otro lado, podríamos incluso ignorar completamente los constructores dejando únicamente el que se genera por defecto y crear los objetos a partir de los métodos ‘get’ y ‘set’ tal que así:


	Date date = new Date();
	date.setYear(2013);
	date.setMonth(8);
	date.setDay(4);
	date.setHour(0);
	date.setMinute(0);
	date.setSecond(0);
	date.setMillisecond(0);

¿Qué desventajas suponen estas dos alternativas?

La primera tiene nombre propio y se conoce como ‘Telescoping constructor anti-pattern‘, sí, es una antipatrón, una de esas soluciones que a priori puede parecer buena pero que a la larga se vuelve en tu contra. Imagina que en lugar de siete propiedades tienes quince, o peor aún, piensa que esas quince propiedades no siguen ningún orden más o menos lógico y te toca ir creando tantos constructores como combinaciones posibles de propiedades, un infierno. Por otro lado, piensa que la sobrecarga en Java tiene ciertos límites y según que combinaciones de propiedades y tipos no podrás repetirlas más de una vez, por ejemplo:


	public Date(int year, int month, int day, int hour) {
		this(year, month, day, hour, 0, 0, 0);
	}

	public Date(int year, int month, int day, int minute) {
		this(year, month, day, 0, minute, 0, 0);
	}

	public Date(int year, int month, int day, int second) {
		this(year, month, day, 0, 0, second, 0);
	}

	public Date(int year, int month, int day, int milisecond) {
		this(year, month, day, 0, 0, 0, milisecond);
	}

Estos cuatros constructores no podrían coexistir al mismo tiempo, los cuatro reciben el mismo número y tipo de parámetros , el compilador no podría deducir cual intentas invocar.

La otra alternativa sería la típica estrategia que se aplica con los JavaBeans, como tal, hereda todas sus desventajas. Es muy común instanciar clases sin haber asignado todas las propiedades necesarias o con estados inválidos, el ejemplo más típico sería crear accidentalmente instancias de nuestra clase ‘Date’ con valores ‘0’ en año, mes o día porque hemos olvidado llamar al método ‘set’ que toca. ¿Quién no ha copiado / pegado estas asignaciones y ha generado algún ‘bug’ que luego ha estado molestando? Por otro lado, esta alternativa elimina la posibilidad de hacer la clase inmutable, algo tan común en nuestros querida clase ‘String‘ y que en según que situaciones puede venirnos muy bien.

¿Cómo solucionamos esta situación?

Para estos casos tenemos el ‘Builder pattern‘, vamos a verlo en acción con nuestra clase ‘Date’:


/**
 * Builder pattern.
 */
public class Date {

	// Todas las propiedades son 'final'
	// por lo tanto nuestro objeto es inmutable.
	private final int year;

	private final int month;

	private final int day;

	private final int hour;

	private final int minute;

	private final int second;

	private final int millisecond;

	// Creamos una clase interna 'Builder'
	public static class Builder {

		// Las propiedades obligatorias se declaran
		// 'final'
		private final int year;

		private final int month;

		private final int day;

		// Las propiedades opcionales se inicializan
		private int hour = 0;

		private int minute = 0;

		private int second = 0;

		private int millisecond = 0;

		// Pasaremos las propiedades obligatorias en el
		// constructor del 'Builder'
		public Builder(int year, int month, int day) {
			this.year = year;
			this.month = month;
			this.day = day;
		}

		// Crearemos un método público para cada una de
		// las propiedades opcionales, éstos se devolverán
		// a sí mismos ('this') para poder encadenar las
		// llamadas
		public Builder withHour(int hour) {
			this.hour = hour;
			return this;
		}

		public Builder withMinute(int minute) {
			this.minute = minute;
			return this;
		}

		public Builder withSecond(int second) {
			this.second = second;
			return this;
		}

		public Builder withMillisecond(int millisecond) {
			this.millisecond = millisecond;
			return this;
		}

		// Este método permitará crear el objeto 'Date'
		// resultante
		public Date build() {
			return new Date(this);
		}
	}

	// Sólo habrá un constructor privado para forzar
	// la creación de los objetos a partir del 'Builder'
	private Date(Builder builder) {
		this.year = builder.year;
		this.month = builder.month;
		this.day = builder.day;
		this.hour = builder.hour;
		this.minute = builder.minute;
		this.second = builder.second;
		this.millisecond = builder.millisecond;
	}

	// Omitimos los siete métodos 'get'
}

Veamos algunos ejemplos de como instanciar ahora esta clase:


	Date date01 = new Date.Builder(2013, 8, 4)
		.build();

	Date date02 = new Date.Builder(2013, 8, 4)
		.withHour(4)
		.build();

	Date date03 = new Date.Builder(2013, 8, 4)
		.withHour(5)
		.withMinute(15)
		.build();

	Date date04 = new Date.Builder(2013, 8, 4)
		.withHour(6)
		.withMinute(30)
		.withSecond(6)
		.build();

	Date date05 = new Date.Builder(2013, 8, 4)
		.withHour(6)
		.withMinute(30)
		.withSecond(6)
		.withMillisecond(300)
		.build();

Aspectos a tener en cuenta:

  • Nuestra código es ahora mucho más limpio y legible, los métodos públicos dentro del ‘Builder’ nos permiten dar mucha más expresividad a sus nombres.
  • La instanciación del objeto se lleva a cabo en una sola linea, de esta manera evitamos estados inconsistentes.
  • Podemos encadenar sin orden específico las asignaciones de las distintas propiedades opcionales mediante las llamada a los métodos oportunos, tantas como sean necesarias.
  • Siempre terminamos la instanciación del objeto con el método ‘build()’, éste se encarga de crear el objeto mediante el constructor privado.
  • Tenemos facilidad para hacer inmutable nuestra clase.
  • Es aconsejable aplicar este patrón como mínimo teniendo cuatro o cinco propiedades, escala muy bien y es fácil añadir nuevas propiedades, sobre todo opcionales.

Pero todo no podían ser ventajas, tener que crear un ‘Builder’ a mano para cada una de nuestras entidades puede llegar a ser bastante lento y tedioso, al fin y al cabo, nuestro IDE genera de forma automática la mayoría de código para constructores, ‘getters’ y ‘setters’, necesitamos una forma eficaz de aplicar este patrón que mostraré en la siguiente entrada.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s