DEV Community

Marco Cobian
Marco Cobian

Posted on

Principios SOLID aplicados en Python

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:

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])
Enter fullscreen mode Exit fullscreen mode

Ejemplo con unica responsabilidad:

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)
Enter fullscreen mode Exit fullscreen mode

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

def compare_two_numbers(a, b): #2 3 1
    if a > b:
        return a
    return b
Enter fullscreen mode Exit fullscreen mode

Ejemplo refactorizado sin aplicar el principio open-close

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
Enter fullscreen mode Exit fullscreen mode

Ejemplo utilizando este principio

def compare_three_numbers(a, b, c):
        more_bigger = compare_two_numbers(compare_two_numbers(a, b), c)
Enter fullscreen mode Exit fullscreen mode

*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

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')
Enter fullscreen mode Exit fullscreen mode

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

# 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()

Enter fullscreen mode Exit fullscreen mode

*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

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()
Enter fullscreen mode Exit fullscreen mode

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í:

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())
Enter fullscreen mode Exit fullscreen mode

Top comments (0)