डिस्क्लेमर: चूंकि अभी तक कोई महान उत्तर नहीं हैं, इसलिए मैंने एक महान ब्लॉग पोस्ट से एक हिस्सा पोस्ट करने का फैसला किया, जिसे मैंने कुछ समय पहले पढ़ा था, लगभग मौखिक रूप से कॉपी किया। आप यहाँ पूर्ण ब्लॉग पोस्ट पा सकते हैं । तो यहाँ है:
हम निम्नलिखित दो इंटरफेस को परिभाषित कर सकते हैं:
public interface IQuery<TResult>
{
}
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
IQuery<TResult>
निर्दिष्ट करता है संदेश है कि डेटा का उपयोग कर देता है के साथ एक विशिष्ट क्वेरी को परिभाषित करता है TResult
सामान्य प्रकार। पहले से परिभाषित इंटरफ़ेस के साथ हम इस तरह एक क्वेरी संदेश को परिभाषित कर सकते हैं:
public class FindUsersBySearchTextQuery : IQuery<User[]>
{
public string SearchText { get; set; }
public bool IncludeInactiveUsers { get; set; }
}
यह वर्ग दो मापदंडों के साथ एक क्वेरी ऑपरेशन को परिभाषित करता है, जिसके परिणामस्वरूप User
वस्तुओं की एक सरणी होगी । इस संदेश को संभालने वाला वर्ग निम्नानुसार परिभाषित किया जा सकता है:
public class FindUsersBySearchTextQueryHandler
: IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
private readonly NorthwindUnitOfWork db;
public FindUsersBySearchTextQueryHandler(NorthwindUnitOfWork db)
{
this.db = db;
}
public User[] Handle(FindUsersBySearchTextQuery query)
{
return db.Users.Where(x => x.Name.Contains(query.SearchText)).ToArray();
}
}
अब हम उपभोक्ताओं को सामान्य IQueryHandler
इंटरफ़ेस पर निर्भर कर सकते हैं :
public class UserController : Controller
{
IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler;
public UserController(
IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler)
{
this.findUsersBySearchTextHandler = findUsersBySearchTextHandler;
}
public View SearchUsers(string searchString)
{
var query = new FindUsersBySearchTextQuery
{
SearchText = searchString,
IncludeInactiveUsers = false
};
User[] users = this.findUsersBySearchTextHandler.Handle(query);
return View(users);
}
}
तुरंत यह मॉडल हमें बहुत लचीलापन प्रदान करता है, क्योंकि हम अब यह तय कर सकते हैं कि किसमें इंजेक्ट किया जाए UserController
। हम UserController
(और उस इंटरफ़ेस के अन्य सभी उपभोक्ताओं) में बदलाव किए बिना, पूरी तरह से अलग कार्यान्वयन को लागू कर सकते हैं, या वास्तविक कार्यान्वयन को लपेटता है ।
IQuery<TResult>
इंटरफ़ेस हमें संकलन-टाइम समर्थन जब निर्दिष्ट करने या इंजेक्शन लगाने देता है IQueryHandlers
हमारे कोड में। जब हम बदले में (लागू करके ) FindUsersBySearchTextQuery
लौटने के लिए बदल जाते हैं , तो संकलन करने में विफल हो जाएगा, क्योंकि सामान्य प्रकार की बाधा पर मैप करने में सक्षम नहीं होगा ।UserInfo[]
IQuery<UserInfo[]>
UserController
IQueryHandler<TQuery, TResult>
FindUsersBySearchTextQuery
User[]
IQueryHandler
हालांकि, एक उपभोक्ता में इंटरफ़ेस इंजेक्ट करना , कुछ कम स्पष्ट समस्याएं हैं जिन्हें अभी भी संबोधित करने की आवश्यकता है। हमारे उपभोक्ताओं की निर्भरता की संख्या बहुत बड़ी हो सकती है और कंस्ट्रक्टर ओवर-इंजेक्शन का कारण बन सकता है - जब एक कंस्ट्रक्टर बहुत सारे तर्क लेता है। क्लास द्वारा निष्पादित किए जाने वाले प्रश्नों की संख्या अक्सर बदल सकती है, जिसके लिए कंस्ट्रक्टर तर्कों की संख्या में निरंतर बदलाव की आवश्यकता होगी।
हम IQueryHandlers
एब्सट्रैक्शन की एक अतिरिक्त परत के साथ बहुत अधिक इंजेक्शन लगाने की समस्या को ठीक कर सकते हैं । हम एक मध्यस्थ बनाते हैं जो उपभोक्ताओं और क्वेरी हैंडलर्स के बीच बैठता है:
public interface IQueryProcessor
{
TResult Process<TResult>(IQuery<TResult> query);
}
IQueryProcessor
एक सामान्य विधि के साथ एक गैर सामान्य इंटरफेस है। जैसा कि आप इंटरफ़ेस परिभाषा में देख सकते हैं, इंटरफ़ेस IQueryProcessor
पर निर्भर करता IQuery<TResult>
है। यह हमें अपने उपभोक्ताओं पर समय का समर्थन करने की अनुमति देता है जो पर निर्भर करता है IQueryProcessor
। आइए UserController
नए का उपयोग करने के लिए फिर से लिखें IQueryProcessor
:
public class UserController : Controller
{
private IQueryProcessor queryProcessor;
public UserController(IQueryProcessor queryProcessor)
{
this.queryProcessor = queryProcessor;
}
public View SearchUsers(string searchString)
{
var query = new FindUsersBySearchTextQuery
{
SearchText = searchString,
IncludeInactiveUsers = false
};
// Note how we omit the generic type argument,
// but still have type safety.
User[] users = this.queryProcessor.Process(query);
return this.View(users);
}
}
UserController
अब एक पर निर्भर करता है IQueryProcessor
कि हमारे सभी जिज्ञासाओं के संभाल कर सकते हैं। UserController
की SearchUsers
प्रणाली को बुलाती है IQueryProcessor.Process
विधि एक initialized क्वेरी ऑब्जेक्ट में गुजर। इंटरफ़ेस को FindUsersBySearchTextQuery
लागू करने के बाद से IQuery<User[]>
, हम इसे जेनेरिक Execute<TResult>(IQuery<TResult> query)
विधि से पारित कर सकते हैं । सी # प्रकार के निष्कर्ष के लिए धन्यवाद, कंपाइलर जेनेरिक प्रकार को निर्धारित करने में सक्षम है और यह हमें स्पष्ट रूप से टाइप करने के लिए बचाता है। Process
विधि का वापसी प्रकार भी ज्ञात है।
अब यह IQueryProcessor
अधिकार प्राप्त करने के कार्यान्वयन की जिम्मेदारी है IQueryHandler
। इसके लिए कुछ डायनामिक टाइपिंग की आवश्यकता होती है, और वैकल्पिक रूप से एक डिपेंडेंसी इंजेक्शन फ्रेमवर्क का उपयोग होता है, और यह सब कोड की कुछ पंक्तियों के साथ किया जा सकता है:
sealed class QueryProcessor : IQueryProcessor
{
private readonly Container container;
public QueryProcessor(Container container)
{
this.container = container;
}
[DebuggerStepThrough]
public TResult Process<TResult>(IQuery<TResult> query)
{
var handlerType = typeof(IQueryHandler<,>)
.MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = container.GetInstance(handlerType);
return handler.Handle((dynamic)query);
}
}
QueryProcessor
कक्षा एक विशिष्ट निर्माण करती IQueryHandler<TQuery, TResult>
आपूर्ति क्वेरी उदाहरण के प्रकार के आधार प्रकार। इस प्रकार का उपयोग आपूर्ति किए गए कंटेनर वर्ग को उस प्रकार का उदाहरण प्राप्त करने के लिए कहने के लिए किया जाता है। दुर्भाग्य से हमें Handle
प्रतिबिंब का उपयोग करने की विधि को कॉल करने की आवश्यकता है (इस मामले में C # 4.0 डायममिक कीवर्ड का उपयोग करके), क्योंकि इस समय हैंडलर उदाहरण को डालना असंभव है, क्योंकि सामान्य TQuery
तर्क संकलन समय पर उपलब्ध नहीं है। हालांकि, जब तक Handle
विधि का नाम बदला नहीं जाता है या अन्य तर्क नहीं मिलते हैं , तब तक यह कॉल कभी भी विफल नहीं होगी और यदि आप चाहते हैं, तो इस वर्ग के लिए एक इकाई परीक्षण लिखना बहुत आसान है। प्रतिबिंब का उपयोग करने से थोड़ी गिरावट आएगी, लेकिन वास्तव में चिंता की कोई बात नहीं है।
आपकी किसी चिंता का जवाब देने के लिए:
इसलिए मैं ऐसे विकल्पों की तलाश कर रहा हूं जो पूरी क्वेरी को एनकैप्सुलेट करता हो, लेकिन फिर भी इतना लचीला हो कि आप केवल कमांड कक्षाओं के विस्फोट के लिए स्पेगेटी रिपोजिटरी को स्वैप नहीं कर रहे हैं।
इस डिजाइन का उपयोग करने का एक परिणाम यह है कि सिस्टम में बहुत सारी छोटी कक्षाएं होंगी, लेकिन बहुत सारी छोटी / केंद्रित कक्षाएं (स्पष्ट नामों के साथ) होना अच्छी बात है। यह दृष्टिकोण स्पष्ट रूप से बेहतर है तो एक रिपॉजिटरी में एक ही विधि के लिए अलग-अलग मापदंडों के साथ कई ओवरलोड हो सकते हैं, जैसा कि आप उन लोगों को एक क्वेरी क्लास में समूहित कर सकते हैं। इसलिए आपको अभी भी एक रिपॉजिटरी में विधियों की तुलना में बहुत कम क्वेरी कक्षाएं मिलती हैं।