Академический Документы
Профессиональный Документы
Культура Документы
Introducción
Principios S.O.L.I.D
Single responsibility
Open-closed
Liskov substitution
Interface segregation
Dependency inversion
Estos principios, aunque eran conocidos anteriormente por separado, fueron popularizados
en este acrónimo por Robert C. Martin.
Su aplicación ayuda a que el código tenga una alta cohesión y un bajo acoplamiento.
Cohesión: Un módulo tiene un alto grado de cohesión si mantiene “unidas” cosas que están
relacionadas entre ellas y mantiene fuera el resto.
Mucha gente no tiene claro estos conceptos, por lo que para dar una definición breve
podríamos decir que tener alta cohesión y bajo acoplamiento ayudan a que el código que
expresa una funcionalidad concreta esté bien unido, pero separado de otras partes ajenas
al mismo.
Esto ayuda a la reutilización y a hacer el código menos frágil, con esto conseguimos que, al
hacer una modificación en una parte del programa, esta no afecte en cascada a muchas
otras partes (fragilidad del código).
Los principios están a un nivel de abstracción por encima de los patrones de diseño y están
también por encima del lenguaje que estemos utilizando, son conceptos a tener en cuenta
mientras programamos.
1
Principios S.O.L.I.D
Lo que nos pide este principio es que cada clase debe tener una única responsabilidad, por
lo que si estamos programando una clase que se ocupa de diferentes cosas es conveniente
partirla en 2 o más clases.
Con esto se consigue mayor mantenibilidad y se clarifica el código, haciéndolo más fácil de
mantenerlo.
Muchas veces estamos tentados de programar demasiadas cosas en una misma clase, pero
esto aumenta el acoplamiento de dos funcionalidades que pueden cambiar por razones
diferentes o en momentos distintos.
Ejemplo Empleado
Tenemos una clase empleado que puede cobrar a los clientes en la caja registradora, pero
también repone el Stock.
A priori no parece mala idea, pero supongamos que nos piden que cambie el proceso de
cobro a clientes añadiendo funcionalidades de pago con tarjeta, por ejemplo. O que el
supermercado crezca y ahora se contrate gente específicamente para reponer el stock de
productos.
En ambos casos tenemos que tocar en la clase empleado, y es posible que una modificación
en una funcionalidad pueda tener efectos colaterales en la otra.
Así las peticiones de cambio serán más sencillas de implementar y una nueva incorporación
al equipo de desarrollo no tendrá tantos problemas para entender el código.
Hay que recordar que, en orientación a objetos, los objetos no tienen necesariamente que
corresponderse con objetos del mundo real, y que, aunque en realidad exista una sola
persona que se ocupe de ambas cosas, podemos crear un objeto para cada rol que
desempeña.
2
Principios S.O.L.I.D
Introducción
La idea de este principio es que las clases están abiertas a la extensión, pero cerradas a la
modificación.
Lo anterior significa que, ante peticiones de cambio en nuestro programa, hay que ser
capaces de añadir funcionalidad sin modificar la existente (siempre que sea posible).
Por lo general, en un mal diseño, modificar una funcionalidad durante el ciclo de vida, suele
conllevar una cadena de cambios en módulos dependientes, y este efecto puede
propagarse en cascada si el código está muy acoplado.
La forma más común de seguir el principio OCP es usar interfaces o clases abstractas de las
que dependen implementaciones concretas.
Ejemplo
3
Principios S.O.L.I.D
Pero implicaría realizar cambios en la empresa, que tiene que aprender a comunicarse con
el nuevo tipo de botella, que probablemente sea muy parecida a la anterior.
EmpresaEmbotelladora
- liquido: BotellaAgua
BotellaAgua BotellaTeHelado
Seguir este principio ayuda a que nuestros sistemas sean más mantenibles en el tiempo y soporten
mejor los cambios, aunque ante todo hay que usar el sentido común.
4
Principios S.O.L.I.D
Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects
y of type S, where S is a subtype of T.
Wikipedia:
Cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias
entre ellas.
Ejemplo
class InterfazVehiculo{
function acelerar();
}
class Camion{
function acelerar() extends InterfazVehiculo{
introducirMasCombustible();
}
}
class Conductor{
function conducir(InterfazVehiculo vehiculo){
// otras funcionalidades…
v.acelerar();
}
}
5
Principios S.O.L.I.D
El conductor tiene un objeto InterfazVehículo cuya instancia pertenece a uno de sus hijos. Pero no
necesita saber a cuál en concreto para hacerlo funcionar.
Seguir este principio hace más sencilla la implementación de nuevas clases hijas.
Anti-ejemplo
Para terminar de entender este principio vamos a ver el ejemplo típico que lo viola.
class Rectangulo{
int base;
int altura;
class Usuario{
function comprobarArea(Rectangulo rectangulo){
rectangulo.setBase(4);
rectangulo.setAltura(5);
if (rectangulo.area() != 20){
//entrar aquí implica que se viola el principio, un cuadrado tendría area 25,
//pero lo esperado es 20 según su interfaz
}
else{
//buenaImplementacion
}
}
}
6
Principios S.O.L.I.D
En este caso si la clase dinámica de Rectangulo es Cuadrado, el cálculo del área será erróneo, lo que
nos forzaría a ensuciar y complicar el código con comprobaciones.
Este ejemplo indica que no se debe mapear automáticamente el mundo real en un modelo
orientado a objetos. No existe una equivalencia unívoca entre ambos modelos.
“Muchas interfaces específicas son mejores que una única más general”
Cuando los clientes son forzados a utilizar interfaces que no usan por completo, están sujetos a
cambios de esa interfaz. Esto al final resulta en un acoplamiento innecesario entre los clientes.
Dicho de otra manera, cuando un cliente depende de una clase que implementa una interfaz cuya
funcionalidad este cliente no usa, pero que otros clientes si usan, este cliente estará siendo afectado
por los cambios que fuercen otros clientes en la clase en cuestión.
Debemos intentar evitar este tipo de acoplamiento cuando sea posible, y esto se consigue
separando las interfaces en otras más pequeñas y específicas.
3. Ejemplos
Hay casos de proyectos que se han vuelto inmantenibles debido a la violación de este principio al
crearse una clase en la que se va metiendo casi toda la funcionalidad en lugar de ir extrayéndola en
diferentes clases.
En ocasiones esta clase suele ser un singleton que está accesible siempre para el resto del programa.
Esto va provocando que casi todos los cambios y bugs se encuentren siempre en la misma clase, y
normalmente arreglar o modificar algo en ella conlleva consecuencias inesperadas en el resto de
esta.
Por si fuera poco, normalmente este tipo de proyectos tienen un testing con muy poca cobertura o
ni siquiera cuentan con él, por lo que encontrar los fallos provocados por arreglar otra parte de
nuestra clase se vuelve muy costoso.
Anti-Ejemplo
Vamos a ver un ejemplo de violación de este principio.
Estamos implementando un zoo, y queremos crear una interfaz que sirva para todas las aves.
7
Principios S.O.L.I.D
Pensamos en loros, flamencos, gaviotas, aves rapaces y gorriones, por lo que implementamos los
métodos de comer y volar.
Posteriormente el zoo consigue presupuesto extra y compra una pareja de avestruces, por lo que
definimos también el método correr.
No nos podemos olvidar tampoco de los pingüinos, necesitamos un método para nadar.
Como no hemos ido refactorizando entre estos pasos, ahora nuestro sistema tiene esta pinta.
Pingüino
Loro
El problema viene con que, por ejemplo, el avestruz tiene que implementar métodos que no usa, y
con la llegada del pingüino tuvo que cambiar innecesariamente para implementar el método de
nadar.
Una forma correcta de haber modelizado el problema sería haber dividido la interfaz en otras más
pequeñas de esta manera
<< Interface >> << Interface >> << Interface >> << Interface >>
iCorredor iPajaroSimple iNadador iVolador
De esta manera cada pájaro concreto solo tiene lo que realmente necesita y se pueden añadir
nuevas clases sin modificar otras zonas que no estén afectadas.
8
Principios S.O.L.I.D
A: Los módulos de alto nivel no deberían depender de los de bajo nivel. Ambos deberían depender
de abstracciones.
B: Las abstracciones no deberían depender de los detalles. Son los detalles los que deberían
depender de abstracciones.
En la orientación a objetos, lo normal es tener una jerarquía de objetos que se unen porque los de
más alto nivel suelen incluir una instancia de los de más bajo nivel.
Un bosque contiene árboles, que a su vez contienen hojas, que contienen células…
Por eso se eligió la palabra “inversión”, porque rompe con esta dinámica.
Lo que se pretende es que no exista la necesidad de que los módulos dependan unos de otros, sino
que dependan de abstracciones. De esta forma, nuestros módulos pueden ser más fácilmente
reutilizables.
Ejemplo
Puede ser un botón físico, uno de una interfaz gráfica en nuestro teléfono, o incluso algún tipo de
sensor.
9
Principios S.O.L.I.D
LamparaConcreta BotonCancreto
Ahora ya no se conocen entre sí el botón y la lámpara pero pueden funcionar juntos. De hecho,
podríamos cambiar fácilmente el objeto que interactúa con el botón para que fuera un motor de
un coche por ejemplo.
La interfaz botón no tiene ni que conocer como funciona el mecanismo del botón concreto ni
saber nada sobre la lámpara.
Algunos estaréis pensando que seguramente nuestro botón esté pensado para utilizarse sólo en
lámparas, por genéricas que estas sean.
Pero esto se puede solucionar con la ayuda de un patrón adaptador que adapte la información
que recibe el objeto con la que espera recibir. Es una manera fácil de trabajar con software de
terceros que no podemos cambiar por ejemplo.
AdaptadorBoton BotonCancreto
LamparaConcreta
10