क्या मैं एनकैप्सुलेशन को तोड़ने के बिना निर्भरता इंजेक्शन का उपयोग कर सकता हूं?


15

यहाँ मेरा समाधान और परियोजनाएं हैं:

  • बुकस्टोर (समाधान)
    • BookStore.Coupler (परियोजना)
      • Bootstrapper.cs
    • BookStore.Domain (परियोजना)
      • CreateBookCommandValidator.cs
      • CompositeValidator.cs
      • IValidate.cs
      • IValidator.cs
      • ICommandHandler.cs
    • BookStore.Infrastructure (परियोजना)
      • CreateBookCommandHandler.cs
      • ValidationCommandHandlerDecorator.cs
    • BookStore.Web (परियोजना)
      • Global.asax
    • BookStore.BatchProcesses (परियोजना)
      • Program.cs

Bootstrapper.cs :

public static class Bootstrapper.cs 
{
    // I'm using SimpleInjector as my DI Container
    public static void Initialize(Container container) 
    {
        container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), typeof(CreateBookCommandHandler).Assembly);
        container.RegisterDecorator(typeof(ICommandHandler<>), typeof(ValidationCommandHandlerDecorator<>));
        container.RegisterManyForOpenGeneric(typeof(IValidate<>),
            AccessibilityOption.PublicTypesOnly,
            (serviceType, implTypes) => container.RegisterAll(serviceType, implTypes),
            typeof(IValidate<>).Assembly);
        container.RegisterSingleOpenGeneric(typeof(IValidator<>), typeof(CompositeValidator<>));
    }
}

CreateBookCommandValidator.cs

public class CreateBookCommandValidator : IValidate<CreateBookCommand>
{
    public IEnumerable<IValidationResult> Validate(CreateBookCommand book)
    {
        if (book.Author == "Evan")
        {
            yield return new ValidationResult<CreateBookCommand>("Evan cannot be the Author!", p => p.Author);
        }
        if (book.Price < 0)
        {
            yield return new ValidationResult<CreateBookCommand>("The price can not be less than zero", p => p.Price);
        }
    }
}

CompositeValidator.cs

public class CompositeValidator<T> : IValidator<T>
{
    private readonly IEnumerable<IValidate<T>> validators;

    public CompositeValidator(IEnumerable<IValidate<T>> validators)
    {
        this.validators = validators;
    }

    public IEnumerable<IValidationResult> Validate(T instance)
    {
        var allResults = new List<IValidationResult>();

        foreach (var validator in this.validators)
        {
            var results = validator.Validate(instance);
            allResults.AddRange(results);
        }
        return allResults;
    }
}

IValidate.cs

public interface IValidate<T>
{
    IEnumerable<IValidationResult> Validate(T instance);
}

IValidator.cs

public interface IValidator<T>
{
    IEnumerable<IValidationResult> Validate(T instance);
}

ICommandHandler.cs

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

CreateBookCommandHandler.cs

public class CreateBookCommandHandler : ICommandHandler<CreateBookCommand>
{
    private readonly IBookStore _bookStore;

    public CreateBookCommandHandler(IBookStore bookStore)
    {
        _bookStore = bookStore;
    }

    public void Handle(CreateBookCommand command)
    {
        var book = new Book { Author = command.Author, Name = command.Name, Price = command.Price };
        _bookStore.SaveBook(book);
    }
}

ValidationCommandHandlerDecorator.cs

public class ValidationCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly ICommandHandler<TCommand> decorated;
    private readonly IValidator<TCommand> validator;

    public ValidationCommandHandlerDecorator(ICommandHandler<TCommand> decorated, IValidator<TCommand> validator)
    {
        this.decorated = decorated;
        this.validator = validator;
    }

    public void Handle(TCommand command)
    {
        var results = validator.Validate(command);

        if (!results.IsValid())
        {
            throw new ValidationException(results);
        }

        decorated.Handle(command);
    }
}

Global.asax

// inside App_Start()
var container = new Container();
Bootstrapper.Initialize(container);
// more MVC specific bootstrapping to the container. Like wiring up controllers, filters, etc..

Program.cs

// Pretty much the same as the Global.asax

समस्या के लिए लंबे सेटअप के लिए क्षमा करें, मेरे पास अपनी वास्तविक समस्या का विवरण देने के अलावा इसे समझाने का कोई बेहतर तरीका नहीं है।

मैं अपना CreateBookCommandValidator नहीं बनाना चाहता public। मैं ऐसा नहीं करूंगा, internalलेकिन अगर मैं इसे बनाता हूं internalतो मैं अपने डीआई कंटेनर के साथ इसे पंजीकृत नहीं कर पाऊंगा। इसका कारण मैं चाहूंगा कि यह आंतरिक हो, क्योंकि एकमात्र परियोजना जिसमें मेरे आईवीडिएट <> की धारणा होनी चाहिए, कार्यान्वयन बुकस्टोर.डोमेन परियोजना में हैं। किसी भी अन्य परियोजना को केवल IValidator <> का उपभोग करने की आवश्यकता है और CompiteValidator को हल किया जाना चाहिए जो सभी मान्यताओं को पूरा करेगा।

बिना एनकैप्सुलेशन के मैं डिपेंडेंसी इंजेक्शन का उपयोग कैसे कर सकता हूं? या मैं इस सब के बारे में गलत कर रहा हूँ?


बस एक नाइटपिक: आप जो उपयोग कर रहे हैं, वह सही कमांड पैटर्न नहीं है, इसलिए इसे कॉल करना कमांड गलत जानकारी हो सकती है। इसके अलावा, CreateBookCommandHandler ऐसा लगता है कि यह LSP को तोड़ रहा है: यदि आप ऑब्जेक्ट पास करते हैं, तो क्या होता है, जो CreateBookCommand से निकलता है? और मुझे लगता है कि आप यहां जो कर रहे हैं वह वास्तव में एनीमिक डोमेन मॉडल एंटीपैटर्न है। बचत जैसी चीजें डोमेन के अंदर होनी चाहिए और सत्यापन इकाई का हिस्सा होना चाहिए।
व्यंग्यात्मक

1
@ यूफ़ोरिक: यह सही है। यह कमांड पैटर्न नहीं है । तथ्य के रूप में, ओपी एक अलग पैटर्न का अनुसरण करता है: कमांड / हैंडलर पैटर्न
स्टीवन

बहुत सारे अच्छे जवाब थे काश मैं जवाब के रूप में अधिक चिह्नित कर सकता था। आपकी मदद के लिए सभी को शुक्रिया।
इवान लार्सन

@ उत्साही, प्रोजेक्ट लेआउट पर दोबारा विचार करने के बाद मुझे लगता है कि कमांडहैंडलर्स डोमेन में होना चाहिए। निश्चित नहीं कि मैंने उन्हें इन्फ्रास्ट्रक्चर प्रोजेक्ट में क्यों रखा। धन्यवाद।
इवान लार्सन

जवाबों:


11

बनाना CreateBookCommandValidatorसार्वजनिक कैप्सूलीकरण का उल्लंघन नहीं करता है, क्योंकि

एन्कैप्सुलेशन का उपयोग किसी वर्ग के अंदर एक संरचित डेटा ऑब्जेक्ट के मान या स्थिति को छिपाने के लिए किया जाता है, जिससे अनधिकृत पार्टियों को सीधे पहुंच से रोका जा सकता है ( विकिपीडिया )

आपका CreateBookCommandValidatorअपने डेटा सदस्यों तक पहुंच की अनुमति नहीं देता है (यह वर्तमान में कोई भी नहीं लगता है) इसलिए इसका उल्लंघन का उल्लंघन नहीं है।

इस वर्ग को सार्वजनिक करना किसी अन्य सिद्धांत (जैसे कि ठोस सिद्धांत) का उल्लंघन नहीं करता है , क्योंकि:

  • उस वर्ग में एक अच्छी तरह से परिभाषित जिम्मेदारी है और इसलिए एकल जिम्मेदारी सिद्धांत का पालन करता है।
  • सिस्टम में नए सत्यापनकर्ताओं को जोड़ना कोड की एक भी पंक्ति को बदलने के बिना किया जा सकता है और इसलिए आप ओपन / बंद सिद्धांत का पालन करें।
  • वह IValidator <T> इंटरफ़ेस जो कि इस वर्ग का कार्यान्वयन संकीर्ण है (केवल एक सदस्य है) और इंटरफ़ेस अलगाव सिद्धांत का अनुसरण करता है।
  • आपके उपभोक्ता केवल उस IValidator पर निर्भर करते हैं <T> इंटरफ़ेस और इसलिए निर्भरता उलटा सिद्धांत का पालन करें।

आप केवल CreateBookCommandValidatorआंतरिक बना सकते हैं यदि कक्षा को पुस्तकालय के बाहर से सीधे उपभोग नहीं किया जाता है, लेकिन यह शायद ही कभी ऐसा होता है, क्योंकि आपकी इकाई परीक्षण इस वर्ग के एक महत्वपूर्ण उपभोक्ता हैं (और आपके सिस्टम में लगभग हर वर्ग)।

यद्यपि आप कक्षा को आंतरिक बना सकते हैं और यूनिट के प्रोजेक्ट प्रोजेक्ट को अपने प्रोजेक्ट के इंटर्नल तक पहुंचने की अनुमति देने के लिए [इंटरनल्सविजनलटीओ] का उपयोग कर सकते हैं, क्यों परेशान होते हैं?

कक्षाओं को आंतरिक बनाने के लिए सबसे महत्वपूर्ण कारण बाहरी पार्टियों (जो आपके पास नियंत्रण नहीं है) को इस तरह की कक्षा पर निर्भरता लेने के लिए रोकना है, क्योंकि यह आपको भविष्य में उस वर्ग को बिना कुछ किए तोड़ने से रोकने से रोक देगा। दूसरे शब्दों में, यह केवल तब होता है जब आप पुन: प्रयोज्य पुस्तकालय (जैसे निर्भरता इंजेक्शन पुस्तकालय) बना रहे हों। तथ्य की बात के रूप में, सरल इंजेक्टर में आंतरिक सामान होता है और इसकी इकाई परीक्षण परियोजना इन इंटर्नल का परीक्षण करती है।

हालाँकि, यदि आप पुन: प्रयोज्य परियोजना नहीं बना रहे हैं, तो यह समस्या मौजूद नहीं है। यह मौजूद नहीं है, क्योंकि आप उन परियोजनाओं को बदल सकते हैं जो इस पर निर्भर हैं, और आपकी टीम के अन्य डेवलपर्स को आपके दिशानिर्देशों का पालन करना होगा। और एक सरल दिशानिर्देश होगा: एक अमूर्त के लिए कार्यक्रम; कार्यान्वयन नहीं (निर्भरता उलटा सिद्धांत)।

जब तक आप एक पुन: प्रयोज्य पुस्तकालय नहीं लिख रहे हैं, तब तक छोटी कहानी, इस वर्ग को आंतरिक मत बनाओ।

लेकिन अगर आप अभी भी इस वर्ग को आंतरिक बनाना चाहते हैं, तब भी आप इसे बिना किसी समस्या के सरल इंजेक्टर के साथ पंजीकृत कर सकते हैं:

container.RegisterManyForOpenGeneric(typeof(IValidate<>),
    AccessibilityOption.AllTypes,
    container.RegisterAll,
    typeof(IValidate<>).Assembly);

केवल यह सुनिश्चित करने के लिए कि आपके सभी सत्यापनकर्ताओं के पास एक सार्वजनिक निर्माणकर्ता है, भले ही वे आंतरिक हों। यदि आप वास्तव में चाहते हैं कि आपके प्रकार में एक आंतरिक कंस्ट्रक्टर हो (तो वास्तव में आप ऐसा क्यों चाहते हैं पता नहीं है) आप कंस्ट्रक्टर रिज़ॉल्यूशन व्यवहार को ओवरराइड कर सकते हैं ।

अपडेट करें

सरल इंजेक्टर v2.6 के बाद से , RegisterManyForOpenGenericसार्वजनिक और आंतरिक दोनों प्रकार के रजिस्टर करने के लिए डिफ़ॉल्ट व्यवहार है। इसलिए आपूर्ति AccessibilityOption.AllTypesअब बेमानी है और निम्नलिखित कथन सार्वजनिक और आंतरिक दोनों प्रकारों को पंजीकृत करेगा:

container.RegisterManyForOpenGeneric(typeof(IValidate<>),
    container.RegisterAll,
    typeof(IValidate<>).Assembly);

8

यह कोई बड़ी बात नहीं है कि CreateBookCommandValidatorवर्ग सार्वजनिक है।

यदि आपको लाइब्रेरी के बाहर इसके उदाहरणों को बनाने की आवश्यकता है जो इसे परिभाषित करता है, तो यह सार्वजनिक वर्ग को बेनकाब करने के लिए एक बहुत ही स्वाभाविक दृष्टिकोण है, और केवल उसी वर्ग के कार्यान्वयन के रूप में उस वर्ग का उपयोग करने वाले ग्राहकों पर भरोसा करें IValidate<CreateBookCommand>। (बस एक प्रकार को उजागर करने का मतलब यह नहीं है कि एनकैप्सुलेशन टूट गया है, यह ग्राहकों को एनकैप्सुलेशन को तोड़ने के लिए थोड़ा आसान बनाता है)।

अन्यथा, यदि आप वास्तव में ग्राहकों को कक्षा के बारे में नहीं जानने के लिए मजबूर करना चाहते हैं, तो आप वर्ग को उजागर करने के बजाय एक सार्वजनिक स्थैतिक कारखाना विधि का उपयोग भी कर सकते हैं, जैसे:

public static class Validators
{
    public static IValidate<CreateBookCommand> NewCreateBookCommandValidator()
    {
        return new CreateBookCommnadValidator();
    }
}

आपके DI कंटेनर में पंजीकरण के लिए, सभी DI कंटेनर जिन्हें मैं एक स्थिर कारखाने विधि का उपयोग करके निर्माण की अनुमति देता हूं।


हाँ, धन्यवाद .. मैं मूल रूप से एक ही बात सोच रहा था इससे पहले कि मैंने यह पोस्ट बनाया। मैं एक फैक्ट्री क्लास बनाने के बारे में सोच रहा था जो उचित आईलीवेट <> कार्यान्वयन को वापस लौटाएगी लेकिन अगर आईवीएलडिएट <> इम्प्लीमेंटेशन में कोई निर्भरता थी, तो संभवत: यह बहुत जल्दी बालों वाली हो जाएगी।
इवान लार्सन

@EvanLarsen क्यों? यदि IValidate<>कार्यान्वयन में निर्भरताएं हैं, तो इन निर्भरताओं को कारखाने विधि के मापदंडों के रूप में डालें।
झोमिनल

5

आप घोषणा कर सकते हैं CreateBookCommandValidatorके रूप में internalएक लागू InternalsVisibleToAttribute ताकि इसे दृश्यमान बनाने के लिए में BookStore.Couplerविधानसभा। यह भी अक्सर यूनिट परीक्षण करते समय मदद करता है।


मुझे उस विशेषता के अस्तित्व का कोई पता नहीं था। धन्यवाद
इवान लार्सन

4

आप इसे आंतरिक बना सकते हैं और आंतरिक अदृश्य का उपयोग कर सकते हैं। वितरित msdn.link ताकि आपका परीक्षण ढांचा / परियोजना इसे एक्सेस कर सके।

मुझे संबंधित समस्या थी -> लिंक

यहाँ समस्या के संबंध में एक और Stackoverflow प्रश्न का लिंक दिया गया है:

और अंत में वेब पर एक लेख


मुझे उस विशेषता के अस्तित्व का कोई पता नहीं था। धन्यवाद
इवान लार्सन

1

एक अन्य विकल्प यह है कि इसे सार्वजनिक किया जाए लेकिन इसे अन्य विधानसभा में रखा जाए।

तो अनिवार्य रूप से, आपके पास एक सेवा इंटरफ़ेस असेंबली, एक सेवा कार्यान्वयन असेंबली (जो सेवा इंटरफ़ेस का संदर्भ देता है), एक सेवा उपभोक्ता असेंबली (जो सेवा इंटरफ़ेस का संदर्भ देता है), और एक IOC रजिस्ट्रार असेंबली (जो सेवा इंटरफेस और सेवा कार्यान्वयन दोनों को एक साथ हुक करने के लिए है) )।

मुझे तनाव देना चाहिए, यह हमेशा सबसे उपयुक्त समाधान नहीं है, लेकिन यह विचार करने लायक है।


क्या इससे इंटर्नल को दिखाई देने वाले सुरक्षा खतरों को दूर किया जा सकता है?
जोहान्स

1
@ जोहान्स: सुरक्षा जोखिम? यदि आप सुरक्षा देने के लिए एक्सेस मॉडिफायर पर भरोसा कर रहे हैं, तो आपको चिंता करने की जरूरत है। आप प्रतिबिंब के माध्यम से किसी भी विधि तक पहुंच प्राप्त कर सकते हैं। लेकिन यह किसी अन्य विधानसभा में लागू किए गए संदर्भों को लागू करते हुए इंटर्नल तक आसान / प्रोत्साहित पहुंच को हटा देता है।
पीडीआर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.