Este es el tercero de cuatro artículos sobre criterios de buen diseño

Introduccion

En primer medida se hara una aclaración de que se entiende por mal diseño. Luego se presentará un problema clásico de desarrollo con una solución no muy acertada y se corregirá la misma expondiendo el criterio que se pretende reflejar.

Audiencia

Desarrolladores

Conocimientos deseados

  • OOP
  • Java
  • OCP
  • LSP

Malos diseños

  • Rigidez

Cambios afectan muchas partes del sistema

  • Fragilidad

Ante un cambio, algunas partes dejan de funcionar

  • Inmovilidad

Dificulad de reuso por estar muy atado a la aplicación en la cual se diseñó

Contexto

Supongan que nos piden que desarrollemos un Logger para nuestro proyecto, ya que algún sector de la empresa exige que se creen archivos de log en algún directorio específico (el requerimiento indica que se tienen que loggear códigos de error, los cuales son números enteros). Lo primero que se nos vendría a la mente sería hacer una clase que reciba un entero e imprima en un archivo de texto lo recibido (El cómo está fuera del alcance e interés del artículo). Haríamos algo asi:

package test.dip;

public class Logger {

	public void log(Integer value) {
		logToFile(value);
	}
}
Que pasaría si nos pidieran en otro momento del proyecto que además de loggear a un archivo de log, lo hagamos también a un Socket?

Podríamos modificar la clase anterior (no estaría bien modificarla, porque la idea es que sea abierta para extensión pero cerrada para la modificación OCP) haciendo que el método log() reciba el output. De esta manera haríamos un if-else para saber a donde loggear. Quedaría asi:

package test.dip;

public class Logger {

	public void log(Integer value, char option) {
		if (option == 'F') {
			logToFile(value);
		}
		else {
			logToSocket(value);
		}
	}
}
Qué es lo que hicimos mal?

  • Agregamos interdependencias al sistema.
  • Hicimos que ante otro requerimiento (por ejemplo loggear vía consola) usemos otro if-else.
  • Otro problema que no tuvimos en cuenta sería el hecho de que siempre estamos loggeando un entero, pero eso podría cambiar entonces estamos atados a un tipo de objeto de entrada sumado ya al tipo de salida. Estamos dependiendo de clases de bajo nivel y no de abstracciones...

Definicion

Modulos de alto nivel nunca debieran depender de modulos de bajo nivel. Ambos deberian depender de abstracciones

Solucion al problema planteado

Podríamos hacer una interfaz de impresión en outputs, que sea independiente del medio físico:

package test.dip;

public interface Appender {

	public void append(Object value);

}

Luego, Logger:

package test.dip;

public class Logger {

	public void log(Object value, Appender appender) {
		appender.append(value);
	}

}
De esta manera Logger es totalmente independiente del input y output (No depende más de un File, depende de una abstracción). Podrían crearse tantos appenders como sea necesario, como por ejemplo:

  1. FileAppender
  2. SocketAppender
  3. JMSAppender
  4. etc

Conclusiones

  • En general, siempre utilizamos librerías de bajo nivel, pero muy pocas veces módulos de alto nivel. Si fuésemos mós cuidadosos a la hora de desarrollar ciertos módulos, otros o nosotros mismos nos beneficiaríamos de lo ya hecho.
  • El código es más sencillo de mantener debido a que no hay relación directa entre clases de bajo nivel, sino a traves de contratos.

Referencia

PDF del principio (external link) Wiki de citerios, específicamente el presente (external link) WikiPedia en inglés (external link)

Autor

nicokiki