Principios SOLID aplicados en Python
Published: 2022-08-11 21:52:52

## Introduccion

**¿En resumne que son los principios de SOLID?**

Bueno los principios de la codificacion de SOLID es un acronimo hecho por Robert C. Martin y se referie a 5 conveciones diferentes de codificacion

Lo que el sugieres es que al seguir estos principios su codigo puede mejorar en confiabilidad, estructurado y su consistencia logica

 Los principios son

- El principio de responsabilidad única (SRP)
- El Principio Abierto-Cerrado (OCP)
- El principio de sustitución de Liskov (LSP)
- El principio de segregación de la interfaz (ISP)
- El principio de inversión de dependencia (DIP)

Estos son una lista de buenas practicas desarrollada a lo largo de los años y se agrupan en siglas al igual que otros terminos como: **DRY** don’t you repeat, o tambien **KISS** mantelo pequeño y simple

---

### Principio de unica responsabilidad

“Una clase debe tener solo una razon para cambiar”

que esto se refiere a que cada funcion del codigo solo debe tener una y solo una unica repsonsabilidad, que en resumen tu funcion debe hacer solo unica cosa al hacer mas de dos cosas en la funcion ya seria necesario dividirla

Ejemplo sin unica responsabilidad:

```python
def get_num_and_bigger(list_of_items):
    list_of_numbers = []

    # create list of only numbers
    for item in list_of_items:
        if isinstance(item, int):
            list_of_numbers.append(item)
    print(list_of_numbers)

    # find bigger number
    bigger_number = max(list_of_numbers)
    print(bigger_number)

get_num_and_bigger([1, 2, "pepe", 9, 10, 6, 7, 8])
```

Ejemplo con unica responsabilidad:

```python
def get_only_numbers(list_of_items):
    list_of_numbers = []

    for item in list_of_items:
        if isinstance(item, int):
            list_of_numbers.append(item)
    return list_of_numbers

def get_bigger_number(list_of_numbers):
    bigger_number = max(list_of_numbers)
    return bigger_number

def main(list_of_items):
    # get list of only numbers
    list_of_numbers = get_only_numbers(list_of_items)
    # get bigger number
    bigger_number = get_bigger_number(list_of_numbers)
    print(bigger_number)
```

---

### Principio de abierto-cerrado

“Las entidades de software… deben estar abiertas a la extensión pero cerradas a la modificación”

esto en resumen dice que lo que ya se tiene desarrollado no es necesario que se rescriba o como mejor lo conocemos refactorizar,simplemente se agregue lo que se necesita ahora, una nueva extension

Mas no significa que ya con esto no se debe modificar el codigo existente cuando las condiciones se necesitan, estoy no es la unica verdad absoluta y como todo en el mundo del desarrollo tiene un “depende”

Tenemos el siguiente ejemplo, que te piden comparar dos numero y obtener el mayor, para eso hacemos la funcion para comparlo, pero que pasa los requerimientos cambian, ahora nos piden calcular el mayor de tres numeros

Ejemplo del comparar dos numeros

```python
def compare_two_numbers(a, b): #2 3 1
    if a > b:
        return a
    return b
```

Ejemplo refactorizado sin aplicar el principio open-close

```python
def compare_two_or_three_numbers(a, b, c=None):
    bigger = b
    if a > b:
        bigger = a
    if c is not None and c > bigger:
        bigger = c
    return bigger
```

Ejemplo utilizando este principio

```python
def compare_three_numbers(a, b, c):
		more_bigger = compare_two_numbers(compare_two_numbers(a, b), c)
```

---

### ****El principio de sustitución de Liskov****

este principio es de los mas dificiles de entender, nos dice que en algun punto de nuestro codigo estamos creando una clase y creamos clases hijas esa hijas deben poder sustituir a la padre y el codigo siga funcionando de la misma forma

Entre todos los principios SOLID, este es el más abstruso de entender y explicar. Para este principio, no existe una solución estándar "similar a una plantilla" donde deba aplicarse, y es difícil ofrecer un "ejemplo estándar" para mostrar.

---

### ****El Principio de Segregación de Interfaz****

“ Muchas interfaces específicas del cliente son mejores que una interfaz de propósito general”

en este principio nos dice que una clase debe tener la interfaz necesaria y evitar metodos que no funcioen o que no tienen que ser parte de esa clase y esto existe ya que avaces se heredan de clases metodos que no se necesitan

Ejemplo de metodo sin sustitucion de liskov, se tiene el problema de que el pinguino es un Ave que hereda de la clase pero en este caso no puede volar

```python
class Bird:
    def fly(self):
        return 'I can fly!'

    def walk(self):
        return 'I can walk!'

class Penguin(Bird):
    def fly(self):
        raise NotImplementedError('Cannot fly')
```

Ejemplo con el metodo sustitucion de liskov, ahora creamos una nueva clase que hereda de Ave la cual son Aves que si pueden volar

```python
# method liskov substitution principle

class BirdNew:
    def walk(self):
        return 'I can walk!'

class BirdCanFly(BirdNew):
    def fly(self):
        return 'I can fly!'

class PenguinNew(BirdNew):
    def walk(self):
        return super().walk()

class Duck(BirdCanFly):
    def fly(self):
        return super().fly()

```

---

### ****El Principio de Inversión de Dependencia****

“Las abstracciones no deben depender de los detalles. Los detalles deben depender de la abstracción. Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deberían depender de abstracciones”

En otras palabras los modulos de alto nivel no deben de depender de odulos de bajo nivel, ambos deben de depender de abstracciones 

```python
class Engine(object):
    def __init__(self):
        pass

    def accelerate(self):
        pass

    def getRPM(self):
        currentRPM = 0
        #...
        return currentRPM

class Vehicle(object):
    def __init__(self):
        self._engine = Engine()

    def getEngineRPM(self):
        return self._engine.getRPM()
```

El código anterior ilustra la manera “habitual” de definir la colaboración entre clases. Como podemos observar, existe una clase `Vehicle` que contiene un objeto de la clase `Engine`. La clase `Vehicle` obtiene las revoluciones del motor invocando el método `getEngineRPM` del objeto Motor y devolviendo su resultado. Este caso se corresponde con una dependencia, el módulo superior `Vehicle` depende del módulo inferior `Engine`, lo cual genera un código tremendamente acoplado y dificil de testear.

Para desacoplar la dependencia `Engine` de `Vehicle` debemos hacer que la clase `Vehicle` deje de responsabilizarse de instanciar el objeto `Engine`, inyectándolo como parámetro al constructor, evitando así que la responsabilidad recaiga sobre la propia clase. De este modo desacoplamos ambos objetos, quedando la clase tal que así:

```python
class Vehicle(object):
    def __init__(self, engine):
        self._engine = engine

    def getEngineRPM(self):
        return self._engine.getRPM()

if __name__ == '__main__':
    vehicle = Vehicle(Engine())
    print(vehicle.getEngineRPM())
```