Python version 3.5 introduit des « indices de type » pour rendre le code plus lisible et permettre aux développeurs de mieux comprendre le code de chacun.
Dans les langages fortement typés comme Java et C, l'inversion de dépendances (DI - Dependency Inversion) est une technologie importante, mais elle est difficile à implémenter dans les langages faiblement typés.
L'idée centrale de l'inversion des dépendances est la suivante : les classes ne doivent pas s'appuyer sur des implémentations spécifiques, mais sur des abstractions. Car les abstractions (interfaces ou classes abstraites) sont des contrats relativement stables.
Mauvais exemple :
<code class="language-python">class GasStation: def fill_tank(car, amount): car.fill(amount)</code>
Dans cet exemple, la station-service ne peut ravitailler que les voitures. Pour aggraver les choses, puisque la fonction fill_tank
n'a pas de type défini, n'importe quelle valeur peut être transmise et l'erreur ne sera découverte qu'au moment de l'exécution.
Bon exemple :
<code class="language-python">from typing import Protocol class Vehicle(Protocol): def fill(amount: int) -> None: ... class GasStation: def fill_tank(vehicle: Vehicle, amount: int) -> None: vehicle.fill(amount)</code>
Dans cet exemple, définissez d'abord la classe abstraite Vehicle
(en utilisant typing.Protocol
). La fonction GasStation
de fill_tank
ne s'appuie plus sur une classe de voiture spécifique, mais sur l'interface Vehicle
, devenant ainsi plus générale et pouvant faire le plein de tout véhicule mettant en œuvre la méthode fill
.
J'ai profité du système d'indication de types de Python et créé une bibliothèque qui simplifie l'utilisation de l'inversion de dépendances, appelée PyDIT (Python Dependency Injection with Types).
Supposons que vous ayez besoin d'une interface de base de données pour stocker les données utilisateur. Que vous utilisiez PostgreSQL, MySQL, OracleDB, une base de données en mémoire ou une base de données NoSQL, vous devez implémenter une classe de connexion à la base de données et fournir les fonctions de lecture, d'écriture et de suppression d'enregistrements. .
<code class="language-python">from time import sleep from typing import TypedDict from typing_extensions import override from uuid import UUID from src.configs.di import pydit from src.adapters.repositories.interfaces.user import UserRepository from src.constants.injection import MEMORY_REPOSITORY_CONFIG_TOKEN from src.domain.user.models.user import UserModel class ConfigType(TypedDict): delay: int class MemoryUserRepository(UserRepository): __users: dict[UUID, UserModel] = {} def __init__(self): self.__delay = self.config.get("delay", 0.2) @pydit.inject(token=MEMORY_REPOSITORY_CONFIG_TOKEN) def config(self) -> ConfigType: # TODO: supress return type error pass @override def get_by_id(self, *, id_: UUID) -> UserModel: sleep(self.__delay) user = self.__users.get(id_) if user is None: raise ValueError("User not found") return user @override def save(self, *, data: UserModel) -> None: sleep(self.__delay) self._check_pk_conflict(pk=data.id) self.__users[data.id] = data @override def list_(self) -> list[UserModel]: return list(self.__users.values()) def _check_pk_conflict(self, *, pk: UUID) -> None: if pk not in self.__users: return raise ValueError("Primary key conflicts: DB alrady has a user with this ID")</code>
Afin de garantir que le code n'a rien à voir avec la technologie des bases de données, définissez une interface que toutes les classes de bases de données doivent suivre :
<code class="language-python">from abc import abstractmethod from typing import Protocol from uuid import UUID from src.domain.user.models.user import UserModel class UserRepository(Protocol): @abstractmethod def get_by_id(self, *, id_: UUID) -> UserModel: pass @abstractmethod def save(self, *, data: UserModel) -> None: pass @abstractmethod def list_(self) -> list[UserModel]: pass</code>
Ensuite, initialisez les dépendances pour l'injection :
<code class="language-python">from src.adapters.repositories.in_memory.user import MemoryUserRepository from src.constants.injection import MEMORY_REPOSITORY_CONFIG_TOKEN from .di import pydit from .get_db_config import get_db_config def setup_dependencies(): pydit.add_dependency(get_db_config, token=MEMORY_REPOSITORY_CONFIG_TOKEN) pydit.add_dependency(MemoryUserRepository, "UserRepository")</code>
Enfin, injectez les dépendances dans le module qui crée l'utilisateur :
<code class="language-python">from typing import cast from src.adapters.repositories.interfaces.user import UserRepository from src.configs.di import pydit from src.domain.user.models.create_user import CreateUserModel from src.domain.user.models.user import UserModel from src.domain.user.services.create import CreateUserService from src.domain.user.services.list import ListUsersService class UserModule: @pydit.inject() def user_repository(self) -> UserRepository: return cast(UserRepository, None) def create(self, data: CreateUserModel) -> None: CreateUserService(self.user_repository).execute(data) def list_(self) -> list[UserModel]: return ListUsersService().execute()</code>
Les dépendances sont injectées en tant que propriétés et sont accessibles via self
ou module.user_repository
.
Cet exemple est simple, mais PyDIT peut être appliqué à une variété de configurations de projet, d'abstractions de code et de scénarios de principe SOLID. Bienvenue pour essayer de contribuer au code !
Référentiel de code : Github
LinkedIn : Marcelo Almeida (MrM4rc)
PyPI : python-pydit
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!