How to Use Dependency Injection (DI) with Abstract Classes in ASP.NET Core
यदि इंटरफेस की जगह एब्स्ट्रेक्ट क्लास को इंप्लीमेंट करके यदि कोई क्लास बनता है तो इस स्थिति में डिपेंडेंसी इंजेक्शन और डिपेंडेंसी की रजिस्ट्रेशन को इस पोस्ट में समझते हैं। ASP.NET Core में डिपेंडेंसी इंजेक्शन (DI) को एब्स्ट्रेक्ट क्लास (Abstract Class) के साथ कैसे उपयोग किया जाता है, इसे एक उदाहरण के साथ समझते हैं।
संकल्पना (Concept)
डिपेंडेंसी इंजेक्शन कंटेनर (DI Container) को इंटरफ़ेस (Interface) के बजाय एब्स्ट्रेक्ट क्लास को भी हल करने के लिए कॉन्फ़िगर किया जा सकता है। सिद्धांत वही रहता है: एक उच्च-स्तरीय क्लास (जैसे CustomerService) एक निम्न-स्तरीय क्लास (जैसे CustomerRepository) पर निर्भर करती है, लेकिन सीधे उसके कंक्रीट इम्प्लीमेंटेशन पर नहीं, बल्कि उसके एब्स्ट्रेक्ट बेस क्लास पर निर्भर करती है।
मुख्य अंतर यह है कि:
- इंटरफ़ेस केवल एक कॉन्ट्रैक्ट (अनुबंध) को परिभाषित करता है, जबकि एब्स्ट्रेक्ट क्लास कॉन्ट्रैक्ट को परिभाषित करने के साथ-साथ कुछ डिफ़ॉल्ट इम्प्लीमेंटेशन या साझा लॉजिक भी प्रदान कर सकता है।
- एब्स्ट्रेक्ट क्लास का सीधे इंस्टेंस नहीं बनाया जा सकता है; इसे हमेशा एक कंक्रीट क्लास द्वारा इनहेरिट (inherit) किया जाना चाहिए।
उदाहरण (Example)
हम पिछले उदाहरण को ही लेंगे, लेकिन ICustomerRepository इंटरफ़ेस की जगह एक BaseCustomerRepository एब्स्ट्रेक्ट क्लास का उपयोग करेंगे।
चरण 1: एब्स्ट्रेक्ट क्लास परिभाषित करें (Define an Abstract Class)
यह एब्स्ट्रेक्ट क्लास डेटा एक्सेस लॉजिक के लिए एक कॉन्ट्रैक्ट बनाता है और इसमें कुछ साझा लॉजिक या एब्स्ट्रेक्ट मेथड हो सकते हैं जिन्हें इनहेरिट करने वाली क्लास को इम्प्लीमेंट करना होगा।
// Abstractions/BaseCustomerRepository.cs
public abstract class BaseCustomerRepository
{
// एक एब्स्ट्रेक्ट मेथड जिसे इनहेरिट करने वाली क्लास को इम्प्लीमेंट करना होगा
public abstract string GetCustomerNameById(int id);
// एक कंक्रीट मेथड जिसे इनहेरिट करने वाली क्लास उपयोग कर सकती है या ओवरराइड कर सकती है
public string GetRepositoryInfo()
{
return "यह एक ग्राहक रिपॉजिटरी है।";
}
}
चरण 2: एब्स्ट्रेक्ट क्लास को लागू करें (Implement the Abstract Class)
यह BaseCustomerRepository एब्स्ट्रेक्ट क्लास का कंक्रीट इम्प्लीमेंटेशन है। यह GetCustomerNameById एब्स्ट्रेक्ट मेथड को इम्प्लीमेंट करेगा।
// Services/CustomerRepository.cs
public class CustomerRepository : BaseCustomerRepository
{
public override string GetCustomerNameById(int id)
{
// यहाँ हम डेटाबेस से ग्राहक का नाम प्राप्त करने का अनुकरण कर रहे हैं
if (id == 1)
{
return "Amit Kumar (Abstract)";
}
else if (id == 2)
{
return "Pooja Singh (Abstract)";
}
else
{
return "Unknown Customer (Abstract)";
}
}
}
चरण 3: कंस्ट्रक्टर इंजेक्शन का उपयोग करने वाली क्लास (Class Using Constructor Injection)
हमारी CustomerService क्लास अब BaseCustomerRepository पर निर्भर करेगी। ध्यान दें कि कंस्ट्रक्टर में टाइप BaseCustomerRepository है।
// Services/CustomerService.cs
public class CustomerService
{
private readonly BaseCustomerRepository _customerRepository;
// कंस्ट्रक्टर इंजेक्शन यहाँ होता है, एब्स्ट्रेक्ट क्लास का उपयोग करके
public CustomerService(BaseCustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
public string GetCustomerDetails(int customerId)
{
string customerName = _customerRepository.GetCustomerNameById(customerId);
string repoInfo = _customerRepository.GetRepositoryInfo(); // एब्स्ट्रेक्ट क्लास से कंक्रीट मेथड का उपयोग
return $"ग्राहक ID: {customerId}, नाम: {customerName}. रिपॉजिटरी जानकारी: {repoInfo}";
}
}
चरण 4: डिपेंडेंसी को रजिस्टर करें (Register Dependencies in Program.cs)
ASP.NET Core के DI कंटेनर में, आप अभी भी एब्स्ट्रेक्ट क्लास को उसके कंक्रीट इम्प्लीमेंटेशन के साथ उसी तरह से रजिस्टर करते हैं जैसे आप एक इंटरफ़ेस के साथ करते हैं।
// Program.cs (ASP.NET Core 6.0+)
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using YourNamespace.Abstractions; // आपके एब्स्ट्रेक्ट क्लास के Namespace को बदलें
using YourNamespace.Services; // आपकी सेवाओं के Namespace को बदलें
var builder = WebApplication.CreateBuilder(args);
// यहाँ हम अपनी डिपेंडेंसी को DI कंटेनर के साथ रजिस्टर करते हैं
// हम BaseCustomerRepository (एब्स्ट्रेक्ट क्लास) को CustomerRepository (कंक्रीट इम्प्लीमेंटेशन) के रूप में रजिस्टर करते हैं।
builder.Services.AddTransient<BaseCustomerRepository, CustomerRepository>();
builder.Services.AddTransient<CustomerService>(); // CustomerService को भी रजिस्टर करें
var app = builder.Build();
// .... अन्य मिडिलवेयर और कॉन्फ़िगरेशन ....
// एक API एंडपॉइंट या MVC कंट्रोलर में इसका उपयोग कैसे किया जाएगा
app.MapGet("/customer-abstract/{id}", ([FromServices] CustomerService customerService, int id) =>
{
return customerService.GetCustomerDetails(id);
});
app.Run();
यह कैसे काम करता है (How it works):
- जब एक HTTP अनुरोध /customer-abstract/1 पर आता है, तो ASP.NET Core app.MapGet में परिभाषित डेलिगेट को एक्सेक्यूट करता है।
- इस डेलिगेट में, हम [FromServices] CustomerService customerService का उपयोग कर रहे हैं, जो DI कंटेनर को CustomerService का एक इंस्टेंस प्रदान करने के लिए कहता है।
- DI कंटेनर देखता है कि CustomerService के कंस्ट्रक्टर को BaseCustomerRepository की आवश्यकता है।
- कंटेनर ने BaseCustomerRepository को CustomerRepository के रूप में पंजीकृत किया है (builder.Services.AddTransient<BaseCustomerRepository, CustomerRepository>();)।
- तो, कंटेनर CustomerRepository का एक नया इंस्टेंस बनाता है (क्योंकि BaseCustomerRepository एक एब्स्ट्रेक्ट क्लास है और इसका सीधे इंस्टेंस नहीं बनाया जा सकता)।
- फिर, यह CustomerRepository इंस्टेंस को CustomerService के कंस्ट्रक्टर में पास करता है।
- इस प्रकार, CustomerService क्लास को BaseCustomerRepository का इंस्टेंस मिल जाता है, जो वास्तव में CustomerRepository का एक ऑब्जेक्ट है।
इंटरफ़ेस बनाम एब्स्ट्रेक्ट क्लास
- इंटरफ़ेस: केवल एक कॉन्ट्रैक्ट (मेथड सिग्नेचर) को परिभाषित करता है। इसमें कोई इम्प्लीमेंटेशन नहीं होता। एक क्लास कई इंटरफेस को इम्प्लीमेंट कर सकती है।
- एब्स्ट्रेक्ट क्लास: कॉन्ट्रैक्ट को परिभाषित कर सकता है और कुछ डिफ़ॉल्ट इम्प्लीमेंटेशन भी प्रदान कर सकता है। इसमें एब्स्ट्रेक्ट और कंक्रीट दोनों तरह के मेथड हो सकते हैं। एक क्लास केवल एक एब्स्ट्रेक्ट क्लास को इनहेरिट कर सकती है।
उपयोग का मामला:
जब आप केवल एक कॉन्ट्रैक्ट को परिभाषित करना चाहते हैं और कोई साझा इम्प्लीमेंटेशन नहीं है, तो इंटरफ़ेस बेहतर है। यह अधिक लचीलापन प्रदान करता है क्योंकि एक क्लास कई इंटरफेस को इम्प्लीमेंट कर सकती है।
जब आपके पास कई संबंधित क्लासें हैं जो कुछ साझा व्यवहार या स्टेट साझा करती हैं, और आप एक बेस इम्प्लीमेंटेशन प्रदान करना चाहते हैं जिसे चाइल्ड क्लासें ओवरराइड कर सकें या उपयोग कर सकें, तो एब्स्ट्रेक्ट क्लास एक अच्छा विकल्प है।
रजिस्ट्रेशन: DI कंटेनर के साथ रजिस्ट्रेशन का तरीका इंटरफ़ेस और एब्स्ट्रेक्ट क्लास दोनों के लिए समान है: आप एब्स्ट्रेक्ट टाइप (इंटरफ़ेस या एब्स्ट्रेक्ट क्लास) को कंक्रीट इम्प्लीमेंटेशन के साथ मैप करते हैं।
संक्षेप में, ASP.NET Core का DI सिस्टम एब्स्ट्रेक्ट क्लास के साथ भी उतनी ही अच्छी तरह से काम करता है जितना इंटरफेस के साथ। चुनाव आपके डिज़ाइन की आवश्यकताओं पर निर्भर करता है कि क्या आपको केवल एक कॉन्ट्रैक्ट की आवश्यकता है या एक साझा बेस इम्प्लीमेंटेशन की भी।
कंक्रीट क्लास को (DI) कंटेनर में सर्विस के रूप में रजिस्टर करना
यदि कोई क्लास बिना किसी इंटरफेस या एब्स्ट्रेक्ट क्लास पर निर्भरता के सर्विस के रूप में प्रदान किया गया है तो डिपेंडेंसी इंजेक्शन कंटेनर मैं किस प्रकार इसको रजिस्टर किया जा सकता है?
आप निश्चित रूप से एक कंक्रीट क्लास को भी डिपेंडेंसी इंजेक्शन (DI) कंटेनर में सर्विस के रूप में रजिस्टर कर सकते हैं, भले ही वह किसी इंटरफ़ेस या एब्स्ट्रेक्ट क्लास पर निर्भर न हो। इसे "सेल्फ-रजिस्ट्रेशन" या "कंक्रीट टाइप रजिस्ट्रेशन" के रूप में जाना जाता है।
कंक्रीट क्लास का रजिस्ट्रेशन:
जब कोई क्लास किसी इंटरफ़ेस या एब्स्ट्रेक्ट क्लास को इम्प्लीमेंट नहीं करती है और आप उसे सीधे उसके कंक्रीट टाइप के रूप में DI कंटेनर में रजिस्टर करना चाहते हैं, तो आप AddTransient, AddScoped, या AddSingleton मेथड के जेनेरिक ओवरलोड का उपयोग कर सकते हैं जिसमें केवल एक टाइप पैरामीटर होता है।
उदाहरण:
मान लीजिए आपके पास एक साधारण LoggingService क्लास है जो किसी इंटरफ़ेस को इम्प्लीमेंट नहीं करती:
// Services/LoggingService.cs
public class LoggingService
{
public void LogMessage(string message)
{
Console.WriteLine($"[LOG]: {message}");
}
}
अब, आप इस LoggingService को अपने Program.cs (या Startup.cs) में DI कंटेनर के साथ रजिस्टर कर सकते हैं:
// Program.cs (ASP.NET Core 6.0+)
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using YourNamespace.Services; // आपके LoggingService के Namespace को बदलें
var builder = WebApplication.CreateBuilder(args);
// LoggingService को DI कंटेनर में रजिस्टर करें
// यहाँ हम LoggingService के टाइप को ही सर्विस और इम्प्लीमेंटेशन दोनों के रूप में निर्दिष्ट करते हैं।
// AddTransient: हर बार जब LoggingService का अनुरोध किया जाता है, तो एक नया इंस्टेंस बनाया जाता है।
// AddScoped: एक HTTP अनुरोध के दायरे में एक ही इंस्टेंस।
// AddSingleton: पूरे एप्लीकेशन के जीवनकाल में एक ही इंस्टेंस।
builder.Services.AddTransient<LoggingService>();
var app = builder.Build();
// एक API एंडपॉइंट या MVC कंट्रोलर में इसका उपयोग कैसे किया जाएगा
app.MapGet("/logtest", ([FromServices] LoggingService loggingService) =>
{
loggingService.LogMessage("यह एक टेस्ट लॉग संदेश है।");
return "लॉग संदेश भेजा गया!";
});
app.Run();
यह कैसे काम करता है?
जब आप builder.Services.AddTransient<LoggingService>(); लिखते हैं, तो आप DI कंटेनर को बताते हैं कि जब कोई LoggingService का इंस्टेंस मांगे, तो उसे LoggingService का ही एक नया इंस्टेंस बनाना चाहिए और प्रदान करना चाहिए।
ASP.NET Core में, जब [FromServices] एट्रिब्यूट का उपयोग करके या कंस्ट्रक्टर इंजेक्शन के माध्यम से LoggingService का अनुरोध किया जाता है, तो DI कंटेनर इसे हल करेगा और आपको LoggingService का एक इंस्टेंस प्रदान करेगा।
इसका उपयोग कब करें?
हालांकि इंटरफेस-आधारित डिपेंडेंसी इंजेक्शन अधिक लचीलापन और टेस्टेबिलिटी प्रदान करता है, कुछ मामलों में सीधे कंक्रीट क्लास को रजिस्टर करना उपयोगी हो सकता है:
- बहुत सरल यूटिलिटी क्लासें: यदि क्लास बहुत छोटी है और इसका इंटरफ़ेस बनाना अनावश्यक जटिलता लगता है, और भविष्य में इसके एकाधिक इम्प्लीमेंटेशन होने की संभावना कम है।
- थर्ड-पार्टी लाइब्रेरी क्लासें: कभी-कभी आप ऐसी क्लासों को इंजेक्ट करना चाहते हैं जो किसी थर्ड-पार्टी लाइब्रेरी से आती हैं और आप उनके लिए इंटरफेस नहीं बना सकते।
- एप्लीकेशन के प्रवेश बिंदु (Entry Points): जैसे कि कुछ कंट्रोलर या रेज़र पेज जहां सीधी डिपेंडेंसी ठीक मानी जाती है।
विचारणीय बातें (Considerations):
- टेस्टेबिलिटी: इंटरफेस के बिना, इस क्लास का यूनिट टेस्ट करते समय मॉक या स्टब का उपयोग करना अधिक कठिन हो जाता है, क्योंकि आप सीधे उस कंक्रीट क्लास पर निर्भर होते हैं। यदि आपको LoggingService के व्यवहार को टेस्ट में बदलना है, तो आपको इसे सीधे बदलना होगा।
- लचीलापन: यदि भविष्य में आपको LoggingService के अलग-अलग इम्प्लीमेंटेशन की आवश्यकता पड़ती है (जैसे, फ़ाइल में लॉगिंग, डेटाबेस में लॉगिंग, क्लाउड में लॉगिंग), तो आपको LoggingService पर सीधे निर्भर रहने वाली सभी जगहों पर कोड बदलना होगा। इंटरफेस के साथ, आप बस रजिस्ट्रेशन बदल सकते थे।
इसलिए, जबकि यह संभव है और कुछ विशिष्ट परिदृश्यों में स्वीकार्य है, सामान्य तौर पर, इंटरफेस-आधारित डिपेंडेंसी इंजेक्शन को प्राथमिकता दी जाती है क्योंकि यह आपके कोड को अधिक लचीला, मेंटेन करने योग्य और टेस्टेबल बनाता है।
टिप्पणियाँ
एक टिप्पणी भेजें