Вы находитесь на странице: 1из 10

Principios S.O.L.I.

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.

Acoplamiento: Acoplamiento es la medida de la fortaleza de la asociación establecida por


una conexión entre módulos dentro de una estructura software.

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

Single Responsibility Principle / Principio de Responsabilidad Única

La idea de este principio es fácil de entender, pero conforme un programa se va haciendo


más grande, es más difícil de implementar.

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 que diseñar un supermercado, cobrarCliente()


una primera aproximación podría ser: reponerStok()

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.

Si seguimos este principio Cajero Reponedor


deberíamos haber hecho
un modelo similar a este. cobrarCliente() reponerStok()

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

Open-Closed Principle / Principio Abierto-Cerrado

Introducción

La definición de OCP es:


Software entities (classes, modules, functions, etc.) should be open for extension, but closed
for modification.

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.

De esta forma puede cambiarse la implementación de la clase concreta manteniéndose la


interfaz intacta.

Ejemplo

Tenemos una empresa que EmpresaEmbotelladora


desde sus comienzos ha BotellaAgua
- liquido: BotellaAgua
estado vendiendo agua
embotellada, y su programa
informático tenía esta forma.

Ahora ha surgido una oportunidad de negocio y quiere empezar a vender botellas de té


helado, por lo que necesita un cambio en su modelo.

Una primera BotellaTeHelado


aproximación sería
algo similar a esto: EmpresaEmbotelladora
- liquido: BotellaAgua
BoellaAgua

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.

Si hubieran seguido el principio Abierto-Cerrado su diagrama de clases habrían utilizado una


interfaz que comunicaría el resto del sistema con las botellas, por lo que, si no se cambia el
api, el resto del sistema puede trabajar ajeno al cambio. Quedaría de esta forma.

EmpresaEmbotelladora
- liquido: BotellaAgua

<< Interface >>


iBotella

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.

Un uso excesivo de interfaces reducirá la productividad y la legibilidad del código.

4
Principios S.O.L.I.D

Liskov Substitution / Sustitución de Liskov

Fue definido formalmente así:

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.

Que traducido a un lenguaje orientado a objetos sería similar a:


– Si a un método “q” le podemos pasar objetos “x” de la clase “T”, el método hace correctamente
su función.

– Si tenemos una clase “S” que hereda de la clase “T”.


Entonces un objeto “y” de la clase “S” debería ser capaz de pasarse a la función “q” y ésta funcionará
igualmente.

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 CocheElectrico extends InterfazVehiculo{


function acelerar(){
incrementarVoltaje();
}
}

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.

En geometría, un cuadrado es un tipo de rectángulo particular que se distingue porque su base y su


altura son iguales.

class Rectangulo{
int base;
int altura;

//añadimos los getter y setter


function area(){
return base * altura;
}
}

class Cuadrado extends Rectangulo{


function setAltura(int altura){
this.altura = altura;
this.base = altura; // en un cuadrado ambos son iguales;
}
function setBase(int base){
this.altura = base; // en un cuadrado ambos son iguales;
this.base =base;
}
}

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.

Interface Segregation Principle / Principio de segregación de interfaz

Para referirse a este principio se suelen usar las frases:

“Muchas interfaces específicas son mejores que una única más general”

“Los clientes no deberían verse forzados a depender de interfaces que no usan”

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

<< Interface >> Avestruz


iPajaro

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

<< Interface >> << Interface >> << Interface >>


Avestruz Pingüino loro

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

Dependency inversion principle / Principio de inversión de dependencias

Estas premisas definen el principio.

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

Consideremos un objeto botón y un objeto lámpara.

El objeto botón reacciona a un estímulo cambiando su estado entre encendido y apagado.

Puede ser un botón físico, uno de una interfaz gráfica en nuestro teléfono, o incluso algún tipo de
sensor.

La lámpara si recibe la orden de encenderse o de apagarse actuará en consecuencia, no importa qué


tipo de lámpara sea.

Un modelo clásico de este sistema Boton Lampara


sería una clase botón que contiene
una instancia de la clase lámpara, a la - lampara: Lampara Encender()
que cambia su estado entre Pulsar() Apagar()
encendida y apagada.

El problema de este modelo es que el botón está demasiado acoplado a la lámpara


innecesariamente, a este botón le costaría encender y apagar un motor por ejemplo, porque ya
tiene la lampara dentro de su estructura.

9
Principios S.O.L.I.D

Vamos a aplicar la inversión de Lampara Boton


dependencias a este sistema a
ver qué conseguimos.

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.

Vamos a aplicar la inversión de Lampara Boton


dependencias a este sistema a
ver qué conseguimos.

AdaptadorBoton BotonCancreto

LamparaConcreta

10

Вам также может понравиться