Рубрики
Без рубрики

Давайте поговорим солидным в Python

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

Вы могли бы услышать срок твердого ведения один или два раза в вашей жизни программирования. Вы, вероятно, даже реализовали принципы твердого вещества в ваших проектах, не зная его! Сегодня мы узнаем, что такое солидно, и почему так важно сделать ваши проекты великолепно. Давайте начнем то, что в аббревиатуре:

S – принцип недействительной ответственности
O – Открытый принцип
L – Принцип замены Лискива
I – принцип сегрегации интерфейса
D – Принцип инверсии зависимости

Необычные имена, время, чтобы увидеть, о чем они все!

Принцип с ограниченной ответственностью (SRP)

Класс должен иметь одну и только одну причину для изменения, а это означает, что класс должен иметь только одну работу.

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

Ниже у вас есть пример класса, который не соответствует SRP:

class Student:
    def avg_grades(self):
        ...

    def sum_missed(self):
        ...

    def save(self):
        ...

Как видите, этот класс делает три разных вещей на основе бизнес-логики:

  • avg_grades принимает средние оценки, полученные студентом,
  • Sum_mised Ссылки на отсутствие во время уроков,
  • Сохранить это техническая операция базы данных.

Чтобы соответствовать SRP, нам нужно разделить тех, так что у класса есть только одна ответственность.

class StudentGradesReport():
    def avg_grades(self, student):
        ...

class StudentAbsenceReport():
    def sum_missed(self, student):
        ...

class StudentDb():
    def save(self, student):
        ...

Открытый закрытый принцип (OCP)

Объекты или объекты должны быть открыты для расширения, но закрываются для модификации.

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

Чтобы показать пример – у нас есть приложение, которое рассчитывает налог на конкретные рыночные продукты. Мы могли бы реализовать это так:

class ProductType(Enum):
    APPLE = auto()
    SUGAR = auto()
    ALCOHOL = auto()

@dataclass
class Product:
    product_type: str
    price: float

def calculate_tax_price(product):
    if product.product_type == ProductType.APPLE:
        return product.price + product.price * 0.05
    elif product.product_type == ProductType.SUGAR:
        return product.price + product.price * 0.08
    elif product.product_type == ProductType.ALCOHOL:
        return product.price + product.price * 0.23

Не только это выглядит уродливым, но и добавление новых продуктов означало бы, что нам нужно изменить существующий код – отсюда разбив OCP. Мы можем исправить это, добавив немного абстракции в наш код и позволяя легко расширение:

@dataclass
class Product(ABC):
    price: float

    @abstractmethod
    def calculate_tax_price(self):
        pass

class Apple(Product):
    product_type = ProducType.APPLE

    def calculate_tax_price(self):
        return self.price + self.price * 0.05

class Sugar(Product):
    product_type = ProducType.SUGAR

    def calculate_tax_price(self):
        return self.price + self.price * 0.08

class Alcohol(Product):
    product_type = ProducType.ALCOHOL

    def calculate_tax_price(self):
        return self.price + self.price * 0.23

Теперь, когда появляется новый продукт, нам не нужно менять какой-либо существующий код, поэтому OCP в порядке.

Принцип замены Liskov (LSP)

Пусть q (x) – недвижимость, доказательство о объектах x типа типа T. Тогда Q (Y) должен быть доказан для объектов y типа S, где S – подтип T.

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

Лучший способ увидеть это, проходит через этот отличный пример, который я нашел в Интернете. Сначала плохой пример (не LSP):

class NormalFile:
    def read(self):
        ...
        print('Reading from regular file')

    def write(self, input_text):
        ...
        print('Writing to regular file')


class ReadonlyFile(NormalFile):
    def read(self):
        ...
        print('Reading from readonly file')

    def write(self):
        raise Exception('Can\'t write to readonly file')


normal_file = NormalFile()
readonly_file = ReadonlyFile()


def make_file_operations(fil, input_text):
    if not isinstance(fil, ReadonlyFile):
        fil.write(input_text)
    fil.read()

make_file_operations(normal_file, 'Bananas are great')
make_file_operations(readonly_file, 'Bananas are great')

ВЫХОД:

Writing to regular file
Reading from regular file
Reading from readonly file

Thi работает, но не соответствует LSP. Не только Напишите Функция имеет разные параметры, но и она ведет себя по-другому. Нам нужно исправить это, чтобы соответствовать LSP, и мы можем сделать это таким образом:

class ReadableFile(ABC):
    @abstractmethod
    def read(self) -> str:
        ...


class WritableFile(ABC):
    @abstractmethod
    def write(self, input_text: str) -> None:
        ...


class NormalFile(ReadableFile, WritableFile):
    def read(self) -> str:
        ...
        print('Reading from file')

    def write(self, input_text: str) -> None:
        ...
        print('Writing to file')


class ReadonlyFile(ReadableFile):
    def read(self) -> str:
        ...
        print('Reading from readonly file')

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

Принцип сегрегации интерфейса (ISP)

Клиент никогда не должен быть вынужден реализовать интерфейс, который он не использует, или клиенты не должны быть вынуждены зависеть от методов, которые они не используют.

Хотя Python не имеет структуры интерфейса, как другие языки программирования, мы все еще можем реализовать этот принцип, используя абстрактные методы (вид). Тем не менее, хорошо понимать ISP, поэтому давайте доберемся до этого (даже если мы его не будем использовать).

Так что такое интерфейс? Интерфейс определяет, какие методы необходимо реализовать в классе, который наследует от него, но не описывает, как – это должно быть сделано классом. Таким образом, принцип сеграмма интерфейса говорит нам, что когда класс наследуется от интерфейса, но не нужны все его методы, мы не должны использовать этот интерфейс для этого класса. Не заставляйте классы для реализации методов, которые им не нужны. Довольно просто.

Принцип инверсии зависимости (DIP)

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

Принцип инверсии зависимости помогает нам создать более эластичный код. При работе с плохими письменными проектами мы можем подойти к проблемам, как:

  • Жесткость – Когда трудно изменить какой-либо кусок кода, потому что он подключен к нескольким другим местам – изменение одной вещи, требует изменения нескольких исходных файлов/классов. Это также означает, что труднее прогнозировать время, необходимое для изменения кода.
  • Хрупкость – Даже простые изменения могут нарушить все приложение или его функциональные возможности. Каждое изменение кода производит несколько ошибок, поэтому нам нужно проводить время на исправлениях.
  • Неподвижность – Когда вы не можете использовать код для других приложений, потому что он написан только для одной проблемы, а не делает его более универсальным.

Чтобы понять эту проблему, мы можем подумать о проблеме отправки сообщения:

class Task:
    def process(self) -> None:
        ...
        email_sender = EmailSender()
        email_sender.send('some nice message')


class EmailSender:
    def send(self, message: str) -> None:
        ...
        print(f'Sending email with message: {message}')

Как вы можете видеть выше, у нас есть Задача класс, который зависит от EmailSender сорт. Одной из проблем, которые мы можем столкнуться, является количество изменений, которые нам нужно было сделать, если мы хотели изменить тип отправителя (используйте другой сервис). Чтобы избежать этой проблемы, мы можем добавить слой абстракции, который будет обработать то, что следует использовать сервис.

class MessageSender(ABC):
    @abstractmethod
    def send(self, message: str) -> None:
        ...


class EmailSender(MessageSender):
    def send(self, message: str) -> None:
        ...
        print(f'Sending email with message: {message}')


class Task:
    def __init__ (self, message_sender: MessageSender):
        self.message_sender = message_sender

    def process(self) -> None:
        self.message_sender.send('some nice message')

Теперь Задача Не заботится, какой отправитель используется, так как он принимает все, что основано на Сообщений Абстрактный класс. Теперь мы даем класс, что он нуждается, вместо класса принял решение по этому вопросу. Это цель инверсии зависимости. Нам нужно понимать, где должна быть зависимость, не создавать код, который трудно изменить.

Оригинал: “https://dev.to/lukzmu/let-s-talk-solid-in-python-2333”