Грамотное внедрение зависимостей
У меня есть небольшая библиотека, в которой я получаю какие-то данные с сайта и затем сериализую их.
Я знаю, что правильным подходом к разработке является применение принципа внедрения зависимостей. В моём коде есть некий базовый класс, в котором есть все методы для работы.
class MyClass:
def __init__(self):
self.my_http_connection = HttpConnection()
def my_method(self):
return self.my_http_connection.get("address")
library = MyClass()
library.my_method()
В конструкторе я создаю экземпляр HttpConnection
, на самом деле, у меня гораздо больше таких экземпляров, которые отвечают за совершенно разные ситуации. Например, класс для хэширования.
Правильно ли таким образом инициализировать классы в конструкторе? Насколько это хорошая практика? Я читал исходные коды redis-py и aiogram и там такие вещи реализованы совершенно другим образом.
Например, любой метод в библиотеке aiogram реализуется через метод __call__
.
Условно
async def send_message(self):
call = SendMessage()
await self(call)
Из-за этого я не могу понять, в каком мне двигаться направлении, чтобы понять, как писать хороший код.
upd: Забыл упомянуть, что я ещё разделяю ответственность классов. Я не делаю так, чтобы в одном классе производились математические вычисления, http-запросы и работа с базой данных. Реальный пример выглядит так:
class Explorer:
def __init__(self, connection: Http):
self.conn = connection
def get(url):
return self.conn.get(url)
class MyClass:
def __init__(self):
self.my_http_connection = Http()
self.serializer = Serializer()
self.explorer = Explorer(self.my_http_connection)
def my_method(self):
return self.serializer.serialize(self.explorer.get("address"))
library = MyClass()
library.my_method()
И вот таким образом я реализую почти весь код. И в классе explorer может быть по аналогии ещё один класс, который отвечает за что-то и т.д.
Ответы (1 шт):
На самом деле нет никакой проблемы в том, чтобы использовать так, как показано у вас на примерах. Кто-то создаёт экземпляры прямо в конструкторе, кто-то, как например, в библиотеке httpx, использует фабричный подход:
def _init_explorer(self, conn: Http) -> Explorer:
return Explorer(conn)
После этого класс будет выглядеть аналогично:
class MyClass:
def __init__(self):
self.my_http_connection = Http()
self.serializer = Serializer()
self.explorer = self._init_explorer(self._my_connection)
def _init_explorer(self, conn: Http) -> Explorer:
return Explorer(conn)
def my_method(self):
return self.serializer.serialize(self.explorer.get("address"))
Никаких ограничений нет, в FastAPI, например, всё инициализируется в конструкторе также как и у вас.
def __init__(self):
# Конечно, в FastAPI конструктор выглядит иначе.
# Это учебный пример
self.router: routing.APIRouter = routing.APIRouter(
routes=routes,
redirect_slashes=redirect_slashes,
dependency_overrides_provider=self,
on_startup=on_startup,
on_shutdown=on_shutdown,
lifespan=lifespan,
default_response_class=default_response_class,
dependencies=dependencies,
callbacks=callbacks,
deprecated=deprecated,
include_in_schema=include_in_schema,
responses=responses,
generate_unique_id_function=generate_unique_id_function,
)
...