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

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

В этом примере у вашего класса FileManager две разные обязанности. Он использует методы .read() и .write() для управления файлом. Он также работает с ZIP-архивами, предоставляя методы .compress() и .decompress() .

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

4. Принцип разделения интерфейсов (ISP)

Принцип разделения интерфейсов (ISP) исходит из того же принципа, что и принцип единственной ответственности. Да, это еще одно перо в шляпе дяди Боба. Основная идея принципа заключается в том, что:

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

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

Рассмотрим следующий пример иерархии классов для моделирования печатных машин:

# printers_isp.py from abc import ABC, abstractmethod class Printer(ABC): @abstractmethod def print(self, document): pass @abstractmethod def fax(self, document): pass @abstractmethod def scan(self, document): pass class OldPrinter(Printer): def print(self, document): print(f"Printing in black and white. ") def fax(self, document): raise NotImplementedError("Fax functionality not supported") def scan(self, document): raise NotImplementedError("Scan functionality not supported") class ModernPrinter(Printer): def print(self, document): print(f"Printing in color. ") def fax(self, document): print(f"Faxing . ") def scan(self, document): print(f"Scanning . ") 

В этом примере базовый класс Printer предоставляет интерфейс, который должны реализовать его подклассы. OldPrinter наследуется от Printer и должен реализовывать тот же интерфейс. Однако OldPrinter не использует методы .fax() и .scan() , поскольку этот тип принтера не поддерживает эти функции.

Читайте также:  Python setup build inplace

Эта реализация нарушает ISP, поскольку она вынуждает OldPrinter предоставлять интерфейс, который класс не реализует или не требует. Чтобы решить эту проблему, вы должны разделить интерфейсы на более мелкие и более конкретные классы. Затем вы можете создавать конкретные классы, наследуя несколько классов интерфейса по мере необходимости:

# printers_isp.py from abc import ABC, abstractmethod class Printer(ABC): @abstractmethod def print(self, document): pass class Fax(ABC): @abstractmethod def fax(self, document): pass class Scanner(ABC): @abstractmethod def scan(self, document): pass class OldPrinter(Printer): def print(self, document): print(f"Printing in black and white. ") class NewPrinter(Printer, Fax, Scanner): def print(self, document): print(f"Printing in color. ") def fax(self, document): print(f"Faxing . ") def scan(self, document): print(f"Scanning . ") 

Теперь Printer , Fax и Scanner являются базовыми классами, которые предоставляют определенные интерфейсы с одной ответственностью каждый. Чтобы создать OldPrinter , вы наследуете только интерфейс Printer . Таким образом, в классе не будет неиспользуемых методов. Чтобы создать класс ModernPrinter , вам нужно наследоваться от всех интерфейсов. Короче говоря, вы разделили интерфейс Printer .

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

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

Принцип инверсии зависимостей (DIP) является последним принципом в наборе SOLID. Этот принцип гласит, что: абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

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

# app_dip.py class FrontEnd: def __init__(self, back_end): self.back_end = back_end def display_data(self): data = self.back_end.get_data_from_database() print("Display data:", data) class BackEnd: def get_data_from_database(self): return "Data from the database" 

В этом примере класс FrontEnd зависит от BackEnd класса и его конкретной реализации. Можно сказать, что оба класса тесно связаны. Эта связь может привести к проблемам с масштабируемостью. Например, предположим, что ваше приложение быстро растет, и вы хотите, чтобы оно могло считывать данные из REST API. Как бы Вы это сделали?

Вы можете подумать о добавлении нового метода BackEnd для получения данных из REST API. Однако для этого также потребуется модифицировать FrontEnd , который должен быть закрыт для модификации по принципу открытости-закрытости.

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

# app_dip.py from abc import ABC, abstractmethod class FrontEnd: def __init__(self, data_source): self.data_source = data_source def display_data(self): data = self.data_source.get_data() print("Display data:", data) class DataSource(ABC): @abstractmethod def get_data(self): pass class Database(DataSource): def get_data(self): return "Data from the database" class API(DataSource): def get_data(self): return "Data from the API" 

В этом перепроектировании ваших классов вы добавили класс DataSource как абстракцию, которая предоставляет требуемый интерфейс или метод .get_data() . Обратите внимание, как FrontEnd теперь зависит от интерфейса, предоставляемого DataSource , который является абстракцией.

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

Вот как вы можете использовать FrontEnd класс в своем коде:

>>> from app_dip import API, Database, FrontEnd >>> db_front_end = FrontEnd(Database()) >>> db_front_end.display_data() Display data: Data from the database >>> api_front_end = FrontEnd(API()) >>> api_front_end.display_data() Display data: Data from the API 

Здесь вы сначала инициализируете FrontEnd с помощью объекта Database , а затем снова с помощью объекта API. Каждый раз, когда вы вызываете .display_data() , результат будет зависеть от конкретного источника данных, который вы используете. Обратите внимание, что вы также можете динамически изменить источник данных, переназначив .data_source атрибут в своем FrontEnd экземпляре.

Заключение

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

В этом уроке вы узнали, как:

  1. Понимать значение и цель каждого принципа SOLID.
  2. Выявлять классы, которые нарушают некоторые принципы SOLID в Python.
  3. Использовать принципы SOLID, чтобы помочь вам реорганизовать код Python и улучшить ООП.

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

Материалы по теме

Источники

Источник

Оцените статью