Введение
Вкратце, что такое принципы 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())