Что такое SOLID

S – Single Responsibility (Принцип единственной ответственности)

Каждый класс должен отвечать только за одну операцию.

O — Open-Closed (Принцип открытости-закрытости)

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

L — Liskov Substitution (Принцип подстановки Барбары Лисков)

Если П является подтипом Т, то любые объекты типа Т, присутствующие в программе, могут заменяться объектами типа П без негативных последствий для функциональности программы.

I — Interface Segregation (Принцип разделения интерфейсов)

Не следует ставить клиент в зависимость от методов, которые он не использует.

D — Dependency Inversion (Принцип инверсии зависимостей)

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

Oct. 9, 2023, Источник

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

S - Single Responsibility Principle - принцип единственной ответственности. Каждый класс должен иметь только одну зону ответственности.

O - Open closed Principle - принцип открытости-закрытости. 
Классы должны быть открыты для расширения, но закрыты для изменения.

L - Liskov substitution Principle - принцип подстановки Барбары Лисков. 
Должна быть возможность вместо базового (родительского) типа (класса) подставить любой его подтип (класс-наследник), при этом работа программы не должна измениться.

I -  Interface Segregation Principle - принцип разделения интерфейсов. 
Данный принцип обозначает, что не нужно заставлять клиента (класс) реализовывать интерфейс, который не имеет к нему отношения.

D - Dependency Inversion Principle - принцип инверсии зависимостей. 
Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те, и другие должны зависеть от абстракции. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Oct. 9, 2023, Источник

SOLID - это аббревиатура от 5 принципов, описанных Робертом Мартином, которые способствуют созданию хорошего объектно-ориентированного (и не только) кода.

S: Single Responsibility Principle (Принцип единственной ответственности).

Каждый класс должен решать лишь одну задачу.

O: Open-Closed Principle (Принцип открытости-закрытости).

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

L: Liskov Substitution Principle (Принцип подстановки Барбары Лисков).

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

I: Interface Segregation Principle (Принцип разделения интерфейса).

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

D: Dependency Inversion Principle (Принцип инверсии зависимостей).

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

  • Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Oct. 9, 2023, Источник

SOLID

SOLID - это акроним, который обозначает пять основных принципов применимых к ООП.

Примеры буду писать на питоне, так как я на нём пишу, лол.

Single-responsibility principle

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

Допустим, возьмем класс который генерирует отчеты и отправляет их на email:

class ReportGenerator:

    def __init__(self):
        self._report: Optional[Report] = None

    def _add_header(self):
        self._report.add("some header")

    def _add_content(self, content: str):
        self._report.add(content)

    def _send(self):
        mailsender.auth("something")
        mailsender.send(self._report)

    def generate(self):
        self._add_header()
        self._add_content("test")
        self._send(self._report)

Такой класс может поменяться по двум причинам - при изменении формата генерации отчета и при изменении механизма отправки. Я думаю, понятно, что этот класс отвечает сразу за 2 вещи, поэтому исходя из SRP их следует разделить:

class ReportSender:

    def send(self, report: Report):
        mailsender.auth("something")
        mailsender.send(report)

class ReportGenerator:

    def __init__(self):
        self._header = "some header"
        self._content_logger = Logger()

    def _add_header(self, report: Report):
        report.add(self._header)

    def _add_content(self, report: Report, content: str):
        report.add(content)
        self._content_logger(content)

    def generate(self) -> Report:
        report = Report()
        self._add_header(report)
        self._add_content(report, "test")
        return self._report

Open–closed principle

OCP - это принцип, устанавливающий правило, в котором сущности (классы, функции и т.д.) должны быть открыты для расширения, но закрыты для изменения.

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

class ReportSender(ABC):

    @abstractmethod
    def send(self, report: Report):
        ...

class EmailReportSender(ReportSender):

    def send(self, report: Report):
        print("Отправили отчет на мыло")

class TelegramReportSender(ReportSender):

    def send(self, report: Report):
        print("Отправили отчет в телегу")

Теперь мы можем создавать новые сущности путем наследования.

Liskov Substitution Principle

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

А если проще, то это значит следующее: допустим, у нас есть класс A и отнаследованный класс B. Если мы заменим все использования класса A на класс B, то ничего не должно измениться, так как класс B всего лишь расширяет класс A. Если нет - вы нарушаете LSP.

А теперь к коду. Допустим у нас есть те же самые классы с отчетами, который были в предыдущем принципе. Добавим несколько новых классов - PrintReportSender и функцию send, которая будет принимать экземпляр класса для осуществления отправки:

class ReportSender(ABC):

    @abstractmethod
    def send(self, report: Report):
        ...

class TelegramReportSender(ReportSender):

    def send(self, report: Report):
        print("Отправили отчет в телегу")

class PrintReportSender:
    # заметили, что мы не унаследываемся от ReportSender?
    def print(self, report: Report):
        print(report)

def send(sender: ReportSender, report: Report):
    sender.send(report)
    print("отправлено")

report = Report()
# Вот тут всё ок, наш telegram_report_sender имеет метод send
telegram_report_sender = TelegramReportSender()
send(telegram_report_sender, report)

# пробуем заменить его на print_report_sender...
print_report_sender = PrintReportSender()
send(print_report_sender, dummy) # всё сломалось

В итоге, telegram_report_sender отработает без проблем, так как он является подтипом ReportSender и реализует метод send, а когда мы вызовем наш print_report_sender всё сломается, так как он не является подтипом ReportSender, ибо не имеет метода send (а только print).

Interface Segregation Principle

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

Самый частый пример - это система ввода вывода. Сделаем класс, который её будет описывать:

class IODevice(ABC):

    @abstractmethod
    def input(self, data: Any):
        ...

    @abstractmethod
    def output(self, data: Any):
        ...

И попробуем сделать клавиатуру наследуясь от этого интерфейса:

class Keyboard(IODevice):

    def input(self, data: Any):
        print("Ну тут понятно, читаем что нам передает клавиатура")

    def output(self, data: Any):
        # а тут что делаем? этот метод явно здесь лишний

В итоге с клавиатуры мы только читаем, а выводить в ней нам ничего не надо. Поэтому операции чтения и записи лучше разделить. Теперь сделаем клавиатуру и монитор:

class InputDevice(ABC):

    @abstractmethod
    def input(self, data: Any):
        ...

class OutputDevice(ABC):

    @abstractmethod
    def output(self, data: Any):
        ...

class Keyboard(InputDevice):

    def input(self, data: Any):
        print("Ну тут понятно, читаем что нам передает клавиатура")

class Monitor(InputDevice, OutputDevice):

    def input(self, data: Any):
        print("Обрабатываем нажатие кнопок на мониторе")

    def output(self, data: Any):
        print("Выводим изображение")

Dependency Inversion Principle

Принцип инверсии зависимостей говорит о том, что классы должны зависеть от абстракций, а не от конкретных реализаций. Если переводить на понятный, то не стоит создавать объекты в классе/функции которая использует эти объекты. Лучше передавать уже готовые. Это уменьшает связанность между классами, делает их более гибкими и более тестируемыми.

Вернёмся к отчётам. Возьмем класс, который имеет сервис отправки нотификаций:

class ReportSender(ABC):

    @abstractmethod
    def send(self, report: Report):
        ...

class TelegramReportSender(ReportSender):

    def send(self, report: Report):
        print("Отправили отчет в телегу")

class EmailReportSender(ReportSender):

    def send(self, report: Report):
        print("Отправили отчет на мыло")

class UserService:

    def __init__(username: str)
        self._username = username
        self._report_sender = TelegramReportSender()

В этом классе мы зависим от конкретной реализации ReportSender'а - от TelegramReportSender. Чтобы такого не было, мы можем передавать реализации ReportSender'а в конструктор:

class UserService:

    def __init__(username: str, report_sender: ReportSender)
        self._username = username
        self._report_sender = report_sender

user_service_with_email = UserService("kiriharu", EmailReportSender())
user_service_with_tg = UserService("kiriharu", TelegramReportSender())

Oct. 24, 2023, notes.kiriha.ru