Принцип инверсии зависимостей python

novikov-nsa / solid_in_python.md

SOLID — это мнемоническая аббревиатура для набора принципов проектирования, созданных для разработки программного обеспечения при помощи объектно-ориентированных языков. Принципы SOLID направленны на содействие разработки более простого, надежного и обновляемого кода. Каждая буква в аббревиатуре SOLID соответствует одному принципу разработки.

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

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

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

Рассмотрим каждый принцип один за другим:

1. Single Responsibility Principle
(Принцип единственной обязанности)

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

# Below is Given a class which has two responsibilities class User: def __init__(self, name: str): self.name = name def get_name(self) -> str: pass def save(self, user: User): pass

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

Мы же просто разделим класс. Мы создадим ещё один класс, который возьмет на себя одну ответственность — управление базой данных пользователя.

class User: def __init__(self, name: str): self.name = name def get_name(self): pass class UserDB: def get_user(self, id) -> User: pass def save(self, user: User): pass

Распространённым решением этой проблемы является применение шаблона проектирования Фасад. Ознакомиться с паттерном Фасад вы можете здесь. User класс был бы фасадом для управления базой данных пользователя и управления свойствами пользователя.

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

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

Давайте представим, что у вас есть магазин, и вы даете скидку в 20% для ваших любимых покупателей используя класс Discount. Если бы вы решаете удвоить 20-ти процентную скидку для VIP клиентов, вы могли бы изменить класс следующим образом:

class Discount: def __init__(self, customer, price): self.customer = customer self.price = price def give_discount(self): if self.customer == 'fav': return self.price * 0.2 if self.customer == 'vip': return self.price * 0.4

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

class Discount: def __init__(self, customer, price): self.customer = customer self.price = price def get_discount(self): return self.price * 0.2 class VIPDiscount(Discount): def get_discount(self): return super().get_discount() * 2

Если вы решите дать скидку супер VIP пользователям, то это будет выглядеть так:

class SuperVIPDiscount(VIPDiscount): def get_discount(self): return super().get_discount() * 2

Расширяйте, но не модифицируйте.

3. Liskov Substitution Principle
(Принцип подстановки Лисков)

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

Более формально: Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.

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

class User(): def __init__(self, color, board): create_pieces() self.color = color self.board = board def move(self, piece:Piece, position:int): piece.move(position) chessmate_check() board = ChessBoard() user_white = User("white", board) user_black = User("black", board) pieces = user_white.pieces horse = helper.getHorse(user_white, 1) user.move(horse)

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

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

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

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

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

class IShape: def draw(self): raise NotImplementedError class Circle(IShape): def draw(self): pass class Square(IShape): def draw(self): pass class Rectangle(IShape): def draw(self): pass

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

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

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

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

class AuthenticationForUser(): def __init__(self, connector:Connector): self.connection = connector.connect() def authenticate(self, credentials): pass def is_authenticated(self): pass def last_login(self): pass class AnonymousAuth(AuthenticationForUser): pass class GithubAuth(AuthenticationForUser): def last_login(self): pass class FacebookAuth(AuthenticationForUser): pass class Permissions() def __init__(self, auth: AuthenticationForUser) self.auth = auth def has_permissions(): pass class IsLoggedInPermissions (Permissions): def last_login(): return auth.last_log

Источник

Python Dependency Inversion Principle

The dependency inversion principle is one of the five SOLID principles in object-oriented programming:

  • S – Single responsibility Principle
  • O – Open-closed Principle
  • L – Liskov Substitution Principle
  • I – Interface Segregation Principle
  • D – Dependency Inversion Principle

The dependency inversion principle states that:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

The dependency inversion principle aims to reduce the coupling between classes by creating an abstraction layer between them.

See the following example:

class FXConverter: def convert(self, from_currency, to_currency, amount): print(f'  = 1.2> ') return amount * 1.2 class App: def start(self): converter = FXConverter() converter.convert('EUR', 'USD', 100) if __name__ == '__main__': app = App() app.start()Code language: Python (python)

In this example, we have two classes FXConverter and App .

The FXConverter class uses an API from an imaginary FX third-party to convert an amount from one currency to another. For simplicity, we hardcoded the exchange rate as 1.2 . In practice, you will need to make an API call to get the exchange rate.

The App class has a start() method that uses an instance of the FXconverter class to convert 100 EUR to USD.

The App is a high-level module. However, The App depends heavily on the FXConverter class that is dependent on the FX’s API.

In the future, if the FX’s API changes, it’ll break the code. Also, if you want to use a different API, you’ll need to change the App class.

To prevent this, you need to invert the dependency so that the FXConverter class needs to adapt to the App class.

To do that, you define an interface and make the App dependent on it instead of FXConverter class. And then you change the FXConverter to comply with the interface.

First, define an abstract class CurrencyConverter that acts as an interface. The CurrencyConverter class has the convert() method that all of its subclasses must implement:

from abc import ABC class CurrencyConverter(ABC): def convert(self, from_currency, to_currency, amount) -> float: pass Code language: Python (python)

Second, redefine the FXConverter class so that it inherits from the CurrencyConverter class and implement the convert() method:

class FXConverter(CurrencyConverter): def convert(self, from_currency, to_currency, amount) -> float: print('Converting currency using FX API') print(f'  = 1.2> ') return amount * 2Code language: Python (python)

Third, add the __init__ method to the App class and initialize the CurrencyConverter ‘s object:

class App: def __init__(self, converter: CurrencyConverter): self.converter = converter def start(self): self.converter.convert('EUR', 'USD', 100)Code language: Python (python)

Now, the App class depends on the CurrencyConverter interface, not the FXConverter class.

The following creates an instance of the FXConverter and pass it to the App :

if __name__ == '__main__': converter = FXConverter() app = App(converter) app.start()Code language: JavaScript (javascript)
Converting currency using FX API 100 EUR = 120.0 USD

In the future, you can support another currency converter API by subclassing the CurrencyConverter class. For example, the following defines the AlphaConverter class that inherits from the CurrencyConverter .

class AlphaConverter(CurrencyConverter): def convert(self, from_currency, to_currency, amount) -> float: print('Converting currency using Alpha API') print(f'  = 1.2> ') return amount * 1.15Code language: Python (python)

Since the AlphaConvert class inherits from the CurrencyConverter class, you can use its object in the App class without changing the App class:

if __name__ == '__main__': converter = AlphaConverter() app = App(converter) app.start()Code language: JavaScript (javascript)
Converting currency using Alpha API 100 EUR = 120.0 USD
from abc import ABC class CurrencyConverter(ABC): def convert(self, from_currency, to_currency, amount) -> float: pass class FXConverter(CurrencyConverter): def convert(self, from_currency, to_currency, amount) -> float: print('Converting currency using FX API') print(f'  = 1.2> ') return amount * 1.15 class AlphaConverter(CurrencyConverter): def convert(self, from_currency, to_currency, amount) -> float: print('Converting currency using Alpha API') print(f'  = 1.2> ') return amount * 1.2 class App: def __init__(self, converter: CurrencyConverter): self.converter = converter def start(self): self.converter.convert('EUR', 'USD', 100) if __name__ == '__main__': converter = AlphaConverter() app = App(converter) app.start() Code language: Python (python)

Summary

  • Use the dependency inversion principle to make your code more robust by making the high-level module dependent on the abstraction, not the concrete implementation.

Источник

Читайте также:  Html код flash player
Оцените статью