14/09/2018 | Desarrollo de software,Tecnologías

"Principio Solid Liskov" ¿Cuáles son los principios S.O.L.I.D.?

El «principio Solid Liskov» el el tercero de los principios S.O.L.I.D., el acrónimo de los cinco principios básicos de la programación orientada a objetos. Los principios S.O.L.I.D. fueron definidos por Robert C. Martin en su publicación “Design Principles and Design Patterns”.

¿Qué significa el acrónimo S.O.L.I.D.?

Single responsibility principle – Principio de responsabilidad única

Open-closed principle – Principio de abierto-cerrado

Liskov substitution principle – Principio de sustitución de Liskov

Interface segregation principle – Principio de segregación de interfaces

Dependency inversion principle – Principio de inversión de dependencias

Liskov substitution principle – Principio de sustitución de Liskov

Continuando con nuestra serie de posts sobre los principios SOLID hoy nos centraremos en el tercero de ellos, el principio de sustitución de Liskov (LSP):

Este principio de la programación orientada a objetos debe su nombre a Barbara Liskov, reconocida ingeniera de software que fue la primera mujer de los Estados Unidos en conseguir un doctorado en Ciencias de la Computación, ganadora de un premio Turing y nombrada doctora honoris causa por la UPM.

Barbara Liskov realizó una primera definición en la conferencia sobre abstracción de datos y jerarquía de 1987:

“Lo que se busca aquí es algo parecido a la siguiente propiedad de sustitución: si por cada objeto o1 del tipo S hay un objeto o2 del tipo T, como aquel para todos los programas P definidos en términos de T, el comportamiento de P no cambia cuando o1 es sustituido por o2, por lo que S es un subtipo de T.”

Posteriormente fue formulado por Barbara Liskov y Jeannette Wing de manera conjunta en un artículo en el año 1994 de la siguiente manera:

“Sea ϕ(x) una propiedad comprobable acerca de los objetos x de tipo T. Entonces ϕ(y) debe ser verdad para los objetos y del tipo S donde S,es un subtipo de T.”

El principio de Liskov nos da una serie de pautas para guiar la herencia entre clases. La principal que debe cumplir si estamos realizando la herencia de una manera correcta es que cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias entre ellas.

Si cuando sobreescribimos un método en la clase que hereda necesitamos lanzar una excepción o no realiza nada, entonces probablemente estamos violando el LSP.

Esta premisa es clave para detectar fallos en la arquitectura de nuestra aplicación o posibles “code smells”.

Veamos un ejemplo que cumple el LSP, creamos una clase padre llamada “License” que gestiona el cálculo del precio de la licencia para un determinado producto y a partir de “License” creamos dos subtipos “PersonalLicense” y “BusinessLicense”, dependiendo si la licencia es para uso personal o de negocios.

Un cliente externo para realizar por ejemplo el cálculo de las facturas solo necesitará conocer la interfaz “License”, los subtipos por su parte utilizarán distintos algoritmos para calcular la tarifa. Cumple el LSP porque el comportamiento de la aplicación externa “Billing” no depende de los subtipos y ambos subtipos son sustituibles por el tipo “License”.

Pero usualmente es más fácil entender el concepto utilizando un ejemplo que viole el principio LSP, uno de los más conocidos es el que supone un tipo “Rectangle” y “Square” como un subtipo de rectángulo.

En el ejemplo que veremos a continuación suponemos que tenemos una clase padre “Bird” que contiene características comunes de los pájaros.

public class Bird {

	public String eat(){
		return food.name;
	}

	public String tweet(){
		return tweet.sound;
	}

	public void fly(){
		// flying functionality
	}

}



A partir de esta clase podemos crear subtipos sin problemas, como por ejemplo los siguientes:

public class Duck extends Bird {

	@Override
	public String eat(){
		return “Fish”;
	}

	@Override
	public String tweet(){
		return “Quack”;
	}

}

public class Sparrow extends Bird {

	@Override
	public String eat(){
		return “Insect”;
	}

	@Override
	public String tweet(){
		return “Chirp”;
	}

}

Puede que pensemos que estamos realizando la herencia correctamente hasta que un día llega un nuevo requisito y tenemos que implementar un nuevo tipo de pájaro, el pingüino.

public class Penguin extends Bird {

	@Override
	public String eat(){
		return “Fish”;
	}

	@Override
	public String tweet(){
		return “Squawk”;
	}

	@Override
	public void fly(){
		throw new Exception(“Penguins can’t fly”);
	}

}

Para controlar la excepción podríamos pensar que es buena idea añadir una regla para saber que tipo de pájaro es en el código que utiliza nuestro modelo Bird:

public void startFlying(Bird bird){
	if(Penguin.class.isInstance(bird)){
		// can’t fly
	}else{
		bird.fly();
	}
}

Pero en este caso estaríamos violando el LSP, porque nos indica que el código debería funcionar sin conocer la clase exacta del objeto “Bird”. Si en un futuro tuviéramos que implementar más casos con excepciones este listado iría creciendo y daría lugar a un código difícil de mantener y más propenso a fallos y errores.

Una solución sería crear otra clase “FlyingBird” que herede de “Bird” y que implemente la función fly() en este caso los subtipos “Duck” y “Sparrow” heredarán de esta clase, y “Penguin” seguirá heredando de “Bird” pero sin tener que implementar la funcionalidad de fly().

public class FlyingBird extends Bird {
	public void fly(){
		// flying functionality
	}
}


Compartir en:

Relacionados