Применение принципов SOLID в Python


Введение

Вкратце, что такое принципы SOLID?

Принципы кодирования SOLID — это аббревиатура, созданная Робертом К. Мартином и относящаяся к 5 различным конвенциям кодирования.

Он говорит о том, что, следуя этим принципам, можно повысить надежность, структуру и логическую последовательность вашего кода.

Принципы таковы

  • Принцип единой ответственности (ПЕ)
  • Принцип открытости-закрытости (OCP)
  • Принцип замещения Лискова (ПЗЛ)
  • Принцип разделения интерфейсов (ISP)
  • Принцип инверсии зависимостей (DIP)

Это список лучших практик, разработанных в течение многих лет и сгруппированных в акронимы, а также другие термины, такие как: DRY не повторяй, или KISS сохраняй его маленьким и простым.


Принцип единой ответственности

«У класса должна быть только одна причина для изменений».

это означает, что каждая функция в коде должна иметь только одну и только одну единственную ответственность, короче говоря, ваша функция должна делать только одну вещь, если в функции делается более двух вещей, ее необходимо разделить.

Пример без единой ответственности:

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])
Войдите в полноэкранный режим Выход из полноэкранного режима

Пример с единой ответственностью:

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)
Войдите в полноэкранный режим Выход из полноэкранного режима

Принцип «открыто-закрыто

«Программные сущности… должны быть открыты для расширения, но закрыты для модификации».

Вкратце это означает, что то, что вы уже разработали, не нужно переписывать или, как мы знаем, лучше рефакторить, вы просто добавляете то, что вам нужно сейчас, новое расширение.

Но это не означает, что вы не должны изменять существующий код, когда это необходимо, это не единственная абсолютная истина, и как все в мире разработки имеет «это зависит».

У нас есть следующий пример, вас просят сравнить два числа и получить большее, для этого мы создаем функцию для сравнения, но что происходит, требования меняются, теперь нас просят вычислить большее из трех чисел.

Пример сравнения двух чисел

def compare_two_numbers(a, b): #2 3 1
    if a > b:
        return a
    return b
Войдите в полноэкранный режим Выход из полноэкранного режима

Пример рефакторинга без применения принципа «открыто-закрыто

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
Войдите в полноэкранный режим Выход из полноэкранного режима

Пример использования этого принципа

def compare_three_numbers(a, b, c):
        more_bigger = compare_two_numbers(compare_two_numbers(a, b), c)
Войдите в полноэкранный режим Выход из полноэкранного режима

*Принцип замещения Лискова

Этот принцип является одним из самых сложных для понимания, он говорит нам, что в какой-то момент в нашем коде мы создаем класс и создаем дочерние классы, что дочерние классы должны иметь возможность заменить родительский и код должен продолжать работать таким же образом.

Среди всех принципов SOLID этот является самым заумным для понимания и объяснения. Для этого принципа не существует стандартного «шаблонного» решения, где его следует применять, и трудно предоставить «стандартный пример» для демонстрации.


*Принцип разделения интерфейсов

«Много интерфейсов, ориентированных на конкретного заказчика, лучше, чем один интерфейс общего назначения».

Этот принцип говорит нам, что класс должен иметь необходимый интерфейс и избегать методов, которые не работают или не нужны, чтобы быть частью этого класса, и это существует потому, что часто методы, которые не нужны, наследуются от классов.

Пример метода без лисковской подстановки, у нас есть проблема, что пингвин — это Ave, который наследуется от класса, но в данном случае он не может летать.

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')
Войдите в полноэкранный режим Выход из полноэкранного режима

Пример с методом подстановки Лискова, теперь мы создаем новый класс, который наследуется от Bird, который является Bird, который может летать.

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

Войдите в полноэкранный режим Выход из полноэкранного режима

*Принцип обратного действия зависимости

«Абстракции не должны зависеть от деталей. Детализация должна зависеть от абстракции. Модули высокого уровня не должны зависеть от модулей низкого уровня. И то, и другое должно зависеть от абстракций».

Другими словами, модули высокого уровня не должны зависеть от модулей низкого уровня, оба должны зависеть от абстракций.

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()
Войдите в полноэкранный режим Выход из полноэкранного режима

Приведенный выше код иллюстрирует «обычный» способ определения взаимодействия между классами. Как мы видим, существует класс Vehicle, который содержит объект класса Engine. Класс Vehicle получает обороты двигателя, вызывая метод getEngineRPM объекта Engine и возвращая его результат. Этот случай соответствует зависимости, верхний модуль Vehicle зависит от нижнего модуля Engine, что порождает чрезвычайно связанный и трудно тестируемый код.

Чтобы отделить зависимость Engine от Vehicle, мы должны сделать так, чтобы класс Vehicle перестал отвечать за инстанцирование объекта Engine, инжектируя его в качестве параметра конструктора, избегая тем самым того, чтобы ответственность ложилась на сам класс. Таким образом, мы разделим оба объекта, оставив класс в таком виде:

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())
Войдите в полноэкранный режим Выход из полноэкранного режима

Оцените статью
devanswers.ru
Добавить комментарий