डिपेंडेंसी इंजेक्शन तकनीक में कई मुख्य लक्ष्य शामिल हैं, (लेकिन इन तक सीमित नहीं):
- आपके सिस्टम के कुछ हिस्सों के बीच युग्मन को कम करना। इस तरह आप कम प्रयास से प्रत्येक भाग को बदल सकते हैं। देखें "उच्च सामंजस्य, कम युग्मन"
- जिम्मेदारियों के बारे में सख्त नियम लागू करना। एक इकाई को अमूर्त के अपने स्तर पर केवल एक ही काम करना चाहिए। अन्य संस्थाओं को इस पर निर्भरता के रूप में परिभाषित किया जाना चाहिए। "आईओसी" देखें
- बेहतर परीक्षण अनुभव। स्पष्ट निर्भरताएं आपको अपने सिस्टम के विभिन्न हिस्सों को कुछ आदिम परीक्षण व्यवहार के साथ स्टब करने की अनुमति देती हैं, जिसमें आपके उत्पादन कोड की तुलना में एक ही सार्वजनिक एपीआई है। देखें "Mocks नहीं हैं 'स्टब्स"
दूसरी बात यह ध्यान रखने की है कि हम आमतौर पर अमूर्तता पर भरोसा करेंगे, कार्यान्वयन पर नहीं। मैं बहुत से ऐसे लोगों को देखता हूं जो केवल विशेष कार्यान्वयन को इंजेक्ट करने के लिए DI का उपयोग करते हैं। बड़ा अंतर है।
क्योंकि जब आप इंजेक्शन लगाते हैं और एक कार्यान्वयन पर भरोसा करते हैं, तो वस्तुओं को बनाने के लिए हम किस पद्धति का उपयोग करते हैं, इसमें कोई अंतर नहीं है। यह सिर्फ मायने नहीं रखता। उदाहरण के लिए, यदि आप requests
उचित अमूर्त के बिना इंजेक्ट करते हैं, तो आपको अभी भी समान विधियों, हस्ताक्षर और वापसी प्रकारों के साथ कुछ भी समान की आवश्यकता होगी। आप इस कार्यान्वयन को बिल्कुल भी बदलने में सक्षम नहीं होंगे। लेकिन, जब आप fetch_order(order: OrderID) -> Order
इसे इंजेक्ट करते हैं तो इसका मतलब है कि कुछ भी अंदर हो सकता है। requests
, डेटाबेस, जो भी हो।
चीजों को योग करने के लिए:
इंजेक्शन का उपयोग करने के क्या फायदे हैं?
मुख्य लाभ यह है कि आपको अपनी निर्भरता को मैन्युअल रूप से इकट्ठा नहीं करना है। हालांकि, यह एक बड़ी लागत के साथ आता है: आप समस्याओं को हल करने के लिए जटिल, यहां तक कि जादुई, उपकरणों का उपयोग कर रहे हैं। एक दिन या कोई और जटिलता आपको वापस लड़ेगी।
क्या यह परेशान और इंजेक्शन फ्रेमवर्क का उपयोग करने के लायक है?
inject
विशेष रूप से रूपरेखा के बारे में एक और बात । मुझे पसंद नहीं है जब मैं वस्तुओं को इंजेक्ट करता हूं जहां इसके बारे में कुछ पता चलता है। यह एक कार्यान्वयन विस्तार है!
Postcard
उदाहरण के लिए, विश्व डोमेन मॉडल में यह कैसे पता चलता है?
मैं punq
सरल मामलों और dependencies
जटिल लोगों के लिए उपयोग करने की सलाह दूंगा।
inject
"निर्भरता" और वस्तु गुणों के स्वच्छ पृथक्करण को भी लागू नहीं करता है। जैसा कि कहा गया था, DI का मुख्य लक्ष्य कठोर जिम्मेदारियों को लागू करना है।
इसके विपरीत, मुझे यह दिखाने दो कि कैसे punq
काम करता है:
from typing_extensions import final
from attr import dataclass
# Note, we import protocols, not implementations:
from project.postcards.repository.protocols import PostcardsForToday
from project.postcards.services.protocols import (
SendPostcardsByEmail,
CountPostcardsInAnalytics,
)
@final
@dataclass(frozen=True, slots=True)
class SendTodaysPostcardsUsecase(object):
_repository: PostcardsForToday
_email: SendPostcardsByEmail
_analytics: CountPostcardInAnalytics
def __call__(self, today: datetime) -> None:
postcards = self._repository(today)
self._email(postcards)
self._analytics(postcards)
देख? हमारे पास कंस्ट्रक्टर भी नहीं है। हम घोषित रूप से अपनी निर्भरता को परिभाषित करते हैं और punq
स्वचालित रूप से उन्हें इंजेक्ट करेंगे। और हम किसी भी विशिष्ट कार्यान्वयन को परिभाषित नहीं करते हैं। केवल प्रोटोकॉल का पालन करें। इस शैली को "कार्यात्मक ऑब्जेक्ट्स" या SRP -styled कक्षाएं कहा जाता है ।
तब हम punq
कंटेनर को स्वयं परिभाषित करते हैं:
# project/implemented.py
import punq
container = punq.Container()
# Low level dependencies:
container.register(Postgres)
container.register(SendGrid)
container.register(GoogleAnalytics)
# Intermediate dependencies:
container.register(PostcardsForToday)
container.register(SendPostcardsByEmail)
container.register(CountPostcardInAnalytics)
# End dependencies:
container.register(SendTodaysPostcardsUsecase)
और इसका उपयोग करें:
from project.implemented import container
send_postcards = container.resolve(SendTodaysPostcardsUsecase)
send_postcards(datetime.now())
देख? अब हमारी कक्षाओं को पता नहीं है कि उन्हें कौन और कैसे बनाता है। कोई सज्जाकार नहीं, कोई विशेष मूल्य नहीं।
SRP- स्टाइल कक्षाओं के बारे में यहाँ पढ़ें:
क्या डोमेन को बाहर से अलग करने के कोई और बेहतर तरीके हैं?
आप जरूरी के बजाय कार्यात्मक प्रोग्रामिंग अवधारणाओं का उपयोग कर सकते हैं। फ़ंक्शन निर्भरता इंजेक्शन का मुख्य विचार यह है कि आप उन चीजों को कॉल नहीं करते हैं जो आपके पास नहीं है। जब आप संदर्भ प्रस्तुत करते हैं, तो आप बाद में इन कॉल को शेड्यूल करते हैं। यहां बताया गया है कि कैसे आप केवल साधारण कार्यों के साथ निर्भरता इंजेक्शन का वर्णन कर सकते हैं:
from django.conf import settings
from django.http import HttpRequest, HttpResponse
from words_app.logic import calculate_points
def view(request: HttpRequest) -> HttpResponse:
user_word: str = request.POST['word'] # just an example
points = calculate_points(user_words)(settings) # passing the dependencies and calling
... # later you show the result to user somehow
# Somewhere in your `word_app/logic.py`:
from typing import Callable
from typing_extensions import Protocol
class _Deps(Protocol): # we rely on abstractions, not direct values or types
WORD_THRESHOLD: int
def calculate_points(word: str) -> Callable[[_Deps], int]:
guessed_letters_count = len([letter for letter in word if letter != '.'])
return _award_points_for_letters(guessed_letters_count)
def _award_points_for_letters(guessed: int) -> Callable[[_Deps], int]:
def factory(deps: _Deps):
return 0 if guessed < deps.WORD_THRESHOLD else guessed
return factory
इस पैटर्न के साथ एकमात्र समस्या यह है कि _award_points_for_letters
रचना करना कठिन होगा।
इसलिए हमने रचना की मदद के लिए एक विशेष आवरण बनाया (यह इसका एक हिस्सा है returns
:
import random
from typing_extensions import Protocol
from returns.context import RequiresContext
class _Deps(Protocol): # we rely on abstractions, not direct values or types
WORD_THRESHOLD: int
def calculate_points(word: str) -> RequiresContext[_Deps, int]:
guessed_letters_count = len([letter for letter in word if letter != '.'])
awarded_points = _award_points_for_letters(guessed_letters_count)
return awarded_points.map(_maybe_add_extra_holiday_point) # it has special methods!
def _award_points_for_letters(guessed: int) -> RequiresContext[_Deps, int]:
def factory(deps: _Deps):
return 0 if guessed < deps.WORD_THRESHOLD else guessed
return RequiresContext(factory) # here, we added `RequiresContext` wrapper
def _maybe_add_extra_holiday_point(awarded_points: int) -> int:
return awarded_points + 1 if random.choice([True, False]) else awarded_points
उदाहरण के लिए, एक शुद्ध कार्य के साथ खुद को रचना करने के लिए RequiresContext
विशेष .map
विधि है। और बस। नतीजतन आपके पास सरल एपीआई के साथ बस सरल कार्य और रचना सहायक हैं। कोई जादू नहीं, कोई अतिरिक्त जटिलता नहीं। और एक बोनस के रूप में सब कुछ ठीक से टाइप और संगत है mypy
।
इस दृष्टिकोण के बारे में यहाँ और पढ़ें: