Archivos de la categoría ‘POO’

El polimorfismo está íntimamente ligado a la inyección de dependencias que vimos en el artículo anterior. Es una de las piezas fundamentales dentro de la programación orientada a objetos, así que intentaré poner un ejemplo muy sencillo para tratar de entender este concepto.
Supongamos que nos encontramos con este simple diagrama UML.

Diagrama UML

Diagrama UML


Como podemos observar, la clase Persona hereda de la superclase Humano, así como la clase Perro hereda de la superclase Animal. Cada una, por supuesto, tendría sus propias propiedades y métodos.
Supongamos también que nos encargan implementar el método “correr” para dichas clases. Podemos deducir que tanto la clase Persona como la clase Perro poseen la particularidad de poder correr, pero cada uno, evidentemente, lo hará de una forma diferente.
Lo inmediatamente lógico es pensar que la solución pasaría por heredar de otra superclase que implementara dicho método pero, como sabemos, Java, así como otros muchos lenguajes de programación, soportan herencia simple, con lo que esta idea cae por su propio peso. Además, aunque fuera posible, el acoplamiento entre las clases resultaría demasiado alto, con lo que la posiblidad de mantenibilidad y extensibilidad de la aplicación se vería en entredicho. Por ello, deberemos recurrir al polimorfismo. Veámoslo con un ejemplo práctico.
Lo primero que haremos será declarar una interface Corredor.
 

public interface Corredor {

	void correr();
	
}

 
La interface contiene la función común a nuestras clases Persona y Perro que, a partir de ahora tendrán que implementarla y por la tanto sobreescribir dicho método.
Así quedarían ambas clases:
 

public class Persona implements Corredor {

	@Override
	public void correr() {
		System.out.println("Yo corro como una persona");		
	}

}

 

public class Perro implements Corredor{

	@Override
	public void correr() {
		
		System.out.println("Yo corro como un perro");
		
	}

}

 
Por motivos prácticos, y para una mayor claridad del ejemplo, he obviado en el código que ambas clases heredarían cada una de su respectiva superclase. Ahora es algo irrelevante para la explicación que nos ocupa.
Como podemos apreciar, en el método “correr” de las clases he puesto simplemente una salida por consola. Con esto será suficiente para ver cómo reacciona nuestro programa.
Ahora vamos a crear una nueva clase a la que he llamado Facade (fachada), que será con la que interactue directamente el usuario de la aplicación.
 

public class Facade {

	public void correr (Corredor c){		
		c.correr();		
	}
	
}

 
La clase Facade cuenta con un método que espera recibir un objeto de tipo Corredor, es decir, que implemente dicha interface. A continuación llama al método correr de dicho objeto, pero ¿a qué método llamará? ¿Cómo sabrá hacía dónde dirigirse?. Pues aquí está lo realmente fascinante del polimorfismo. Como tanto la clase Persona como la clase Perro implementan la interface Corredor, al pasar una instancia de cualquiera de ellas como argumento, reaccionará llamando directamente al método “correr” de la clase correspondiente, sin importarle realmente de qué objeto se trata. Él solo sabe que debe llamar al método correr del objeto Corredor que le ha sido pasado.
ATENCIÓN: Aunque he llamado “correr” al método de esta clase Facade, se podría haber llamado de cualquier otra forma. En este caso no tiene nada que ver con la interface que hemos implementado anteriormente.
Por último, para terminar nuestro programa, nos falta crear nuestro punto de entrada.
 

public class Sistema {

	public static void main(String[] args) {
		
		Persona persona = new Persona();
		Perro perro = new Perro();
		
		Facade fachada = new Facade();
		fachada.correr(persona);
		fachada.correr(perro);
		
	}

}

 
Instanciamos un objeto de cada una de nuestras clases Persona y Perro. A continuación instanciamos la clase Facade, y le pasamos cada una de las instancias anteriores como argumento de su método “correr”, que como ya sabemos está esperando un objeto que implemente la interfaz Corredor.
Compilamos y tenemos la siguiente salida:
 

salida por consola

salida por consola


 
Vemos que, efectivamente, ha respondido el método “correr” de cada una de las clases. De esta forma, si mañana nos piden implementar una nueva clase Gato, no tendremos que tocar absolutamente nada del código que ya tenemos hecho. El sistema seguirá funcionando exactamente igual. Tan solo crearemos nuestra clase Gato implementando la interfaz Corredor, y declararemos aquello que nuestro Gato queramos que haga en su método “correr”. Así de sencillo.
Espero que el ejemplo, aunque muy simple, haya dejado claro las virtudes y beneficios de usar polimorfismo en nuestras aplicaciones.
Un saludo.-

Diseño de aplicaciones

Inyección de dependencias

Uno de los pilares de la programación orientada a objetos es la búsqueda del bajo acomplamiento entre las clases. Con esto entendemos que cualquier dependencia que una clase tenga sobre otra, en un futuro va a resultar en un problema a la hora de la escalabilidad y extensibilidad de la aplicación.
Para resolver dicho problema contamos con el patrón llamado inyección de dependencias (Dependency injection en inglés). En proyectos pequeños es posible que el uso de dicho patrón no sea necesario, pero en el caso de proyectos medianamente grandes, recurrir a él va a ser imprescindible. El problema en estos casos no es tanto saber qué es la inyección de dependencias sino cuándo, cómo y dónde implementarlo.
Así pues, vamos a ver un ejemplo muy sencillo donde voy a tratar de explicarlo.
Supongamos que tenemos un hardware cualquiera, el cual espera de un componente, desarrollado por un proveedor cualquiera, para poder funcionar. Imaginemos que, en un principio, disponemos de dos componentes distintos, los cuales trabajan de forma diferente para proveer al hardware de diversas funcionalidades. Con este punto de partida, lo primero que se nos ocurriría sería crear una clase Hadware que en su interior instanciara un objeto de un componente u otro según el caso. Hasta ahí bien. Pero, ¿qué ocurriría si mañana disponemos de mil componentes? Si además nos encontramos con que los componentes pueden cambiar la forma de hacer las cosas, la mantenibilidad se vería en entredicho. Tendríamos que modificar código practicamente en todo el programa, incluída por supuesto la clase Hardware. Queda claro que no es viable, que el diseño de la aplicación no es ni mucho menos elegante, y que no se respetan los principios de la programación orientada a objetos.
Dicho esto veamos cómo acometer este problema usando, evidentemente, la inyección de dependencias.
Primero crearemos una interface.
 

public interface Hardware {
	void Initialization();
	void Shutdown();
	void Prepare();
	void DoIt();
}

 
Esta interface contiene los cuatro métodos que el Hardware ha declarado para poder funcionar. Dichos métodos tendrán que ser implementados obligatoriamente por cualquier componente con el que vaya a interactuar. Crearemos ahora esos componentes que implementarán la interface.
 

public class ComponenteA implements Hardware{
	
	public void Initialization() {
		
		System.out.println("Iniciando... (componente A)");
		
	}

	public void Shutdown() {
		
		System.out.println("Apagando... (componente A)");
		
	}

	public void Prepare() {
		
		System.out.println("Preparando... (componente A)");
		
	}

	public void DoIt() {
		
		System.out.println("Trabajando... (componente A)");
		
	}

}

 

public class ComponenteB implements Hardware{

	public void Initialization() {
		
		System.out.println("Iniciando... (componente B)");
		
	}

	public void Shutdown() {
		
		System.out.println("Apagando... (componente B)");
		
	}

	public void Prepare() {
		
		System.out.println("Preparando... (componente B)");
		
	}

	public void DoIt() {
		
		System.out.println("Trabajando... (componente B)");
		
	}

}

 
Como podemos ver, he puesto una simple salida por consola en cada método para poder probar su funcionamiento.
Ahora, para que el cliente no acceda directamente a la lógica de negocio, crearemos una clase “fachada” usando el patrón Facade. Esto hará que el cliente interactue con él, ocultando toda la “sala de máquinas” de nuestro programa.
 

public class ProductFacade {

	private Hardware SomeHardware;
	
	public ProductFacade(Hardware SomeHardware)
	{
		this.setSomeHardware(SomeHardware);
	}

	public Hardware getSomeHardware() {
		return SomeHardware;
	}

	public void setSomeHardware(Hardware someHardware) {
		SomeHardware = someHardware;
	}	
	
}

 
Veamos esta última clase con detenimiento.
Declaramos una variable de tipo Hardware privada (encapsulamiento ante todo). Seguidamente tenemos el constructor que espera recibir un objeto de tipo Hardware, es decir, de cualquier clase que implemente la interface Hardware que hemos declarado al principio. Seguidamente terminamos con los getter y setter correspondientes, que nos permitirán acceder al objeto en cuestión y a todos sus métodos.
Ya solo nos queda implementar el punto de entrada, aquel que ejecutará el cliente.
 

public class Main {

	public static void main(String[] args) {

		Hardware componentA = new ComponenteA();
		ProductFacade product01 = new ProductFacade(componentA);
		product01.getSomeHardware().Initialization();
		product01.getSomeHardware().Prepare();
		product01.getSomeHardware().DoIt();
		product01.getSomeHardware().Shutdown();		
		
		Hardware componentB = new ComponenteB();
		ProductFacade product02 = new ProductFacade(componentB);
		product02.getSomeHardware().Initialization();
		product02.getSomeHardware().Prepare();
		product02.getSomeHardware().DoIt();
		product02.getSomeHardware().Shutdown();	

	}

}

 
Como podemos ver, instanciamos un objeto del componenteA y posteriormente se lo pasamos directamente al constructor de la instanciación de nuestra clase “fachada”. A partir de ahí ya podremos acceder a los métodos del componente inyectado a través de su getter.
A efectos prácticos, he instanciado los dos componentes consecutivamente para que podamos comprobar por consola cómo se comporta nuestro programa en cada momento.
Si ejecutamos el proyecto, la salida será la siguiente:
 

Salida por consola

Salida por consola


 
Las ventajas de usar este patrón son evidentes. A partir de ahora, si tenemos más componentes, no nos importará. Tan solo deberemos inyectar en cada momento el componente deseado a la clase “fachada” del método main. En el caso de que algún componente cambiara su funcionalidad, solo deberiamos efectuar los ajustes correspondientes en el método o métodos de su clase, sin tocar nada de código exterior a ella.
Mínimo esfuerzo en una aplicación escalable y mantenible. Eso es lo bueno de trabajar correctamente con la programación orientada a objetos.
Un saludo.-