2012-03-27 12 views
49

मैं प्रकृति में क्षणिक कंटेनर से वस्तुओं को कैसे खींच सकता हूं? क्या मुझे उन्हें कंटेनर के साथ पंजीकृत करना है और आवश्यकता वर्ग के निर्माता में इंजेक्ट करना है? कन्स्ट्रक्टर में सबकुछ इंजेक्शन करना अच्छा नहीं लगता है। इसके अलावा सिर्फ एक वर्ग के लिए मैं TypedFactory नहीं बनाना चाहता हूं और कारखाने को आवश्यकता वर्ग में इंजेक्ट करना चाहता हूं।विंडसर - कंटेनर से क्षणिक वस्तुओं को खींचना

एक और विचार जो मेरे पास आया था, उन्हें "नए" की जरूरत थी। लेकिन मैं अपने सभी वर्गों में Logger घटक (संपत्ति के माध्यम से) इंजेक्शन भी दे रहा हूं। इसलिए यदि मैं उन्हें नया करता हूं, तो मुझे उन वर्गों में मैन्युअल रूप से Logger को तुरंत चालू करना होगा। मैं अपने सभी वर्गों के लिए कंटेनर का उपयोग कैसे जारी रख सकता हूं?

लॉगर इंजेक्शन: मेरी कक्षाओं के अधिकांश Logger संपत्ति परिभाषित, को छोड़कर, जहां वहाँ (उस मामले केवल आधार वर्ग इस संपत्ति है में, और सभी पाने क्लासेस का उपयोग करें) विरासत श्रृंखला है। जब इन्हें विंडसर कंटेनर के माध्यम से तत्काल किया जाता है, तो उन्हें इन्हें ILogger में इंजेक्शन दिया जाएगा।

//Install QueueMonitor as Singleton 
Container.Register(Component.For<QueueMonitor>().LifestyleSingleton()); 
//Install DataProcessor as Trnsient 
Container.Register(Component.For<DataProcessor>().LifestyleTransient()); 

Container.Register(Component.For<Data>().LifestyleScoped()); 

public class QueueMonitor 
{ 
    private dataProcessor; 

    public ILogger Logger { get; set; } 

    public void OnDataReceived(Data data) 
    { 
     //pull the dataProcessor from factory  
     dataProcessor.ProcessData(data); 
    } 
} 

public class DataProcessor 
{ 
    public ILogger Logger { get; set; } 

    public Record[] ProcessData(Data data) 
    { 
     //Data can have multiple Records 
     //Loop through the data and create new set of Records 
     //Is this the correct way to create new records? 
     //How do I use container here and avoid "new" 
     Record record = new Record(/*using the data */); 
     ... 

     //return a list of Records  
    } 
} 


public class Record 
{ 
    public ILogger Logger { get; set; } 

    private _recordNumber; 
    private _recordOwner; 

    public string GetDescription() 
    { 
     Logger.LogDebug("log something"); 
     // return the custom description 
    } 
} 

सवाल:

  1. मैं कैसे "नया" का उपयोग किए बिना नई Record वस्तु बना सकता हूँ?

  2. QueueMonitorSingleton है, जबकि Data "स्कॉप्ड" है। मैं को OnDataReceived() विधि में इंजेक्ट कैसे कर सकता हूं?

+0

@Steven मैंने लॉगर उपयोग दिखाते हुए एक कोड नमूना जोड़ा। क्या आपको लगता है कि यह एक खराब डिजाइन है? – user1178376

+1

क्या आप कंक्रीट कोड के साथ चित्रित कर सकते हैं, यहां तक ​​कि एक परीक्षण भी बेहतर है, जिसे आप प्राप्त करने की कोशिश कर रहे हैं? –

+0

खराब तरीके से मेरे प्रश्न का निर्माण करने के लिए खेद है। मैंने अपने मामले की व्याख्या करने के लिए और कोड जोड़ा। – user1178376

उत्तर

211

नमूने आप इसे देने से बहुत विशिष्ट होना कठिन है, लेकिन सामान्य रूप में, जब आप सबसे सेवाओं में ILogger उदाहरणों इंजेक्षन, आप अपने आप को दो चीजों से पूछना चाहिए:

  1. मैं भी लॉग ऑन करें बहुत?
  2. क्या मैं ठोस सिद्धांतों का उल्लंघन करता हूं? इस तरह

    try 
    { 
        // some operations here. 
    } 
    catch (Exception ex) 
    { 
        this.logger.Log(ex); 
        throw; 
    } 
    

    लेखन कोड चिंता से आता है:

1. क्या मुझे बहुत ज्यादा

आप बहुत अधिक प्रवेश कर रहे हैं, जब आप इस तरह कोड का एक बहुत कुछ है लॉग इन करें त्रुटि जानकारी खोने का। हालांकि, इस जगह के सभी प्रकार के प्रयास-पकड़ ब्लॉक को डुप्लिकेट करना, मदद नहीं करता है। इससे भी बदतर, मैं अक्सर डेवलपर्स को लॉग इन करता हूं और जारी रखता हूं (वे अंतिम throw कथन हटाते हैं)। यह वास्तव में खराब है (और पुराने वीबी ON ERROR RESUME NEXT की तरह गंध करता है), क्योंकि ज्यादातर परिस्थितियों में आपके पास यह निर्धारित करने के लिए पर्याप्त जानकारी नहीं है कि यह सुरक्षित है या नहीं। अक्सर कोड में एक बग है जिससे ऑपरेशन विफल हो जाता है। जारी रखने का मतलब है कि उपयोगकर्ता को अक्सर यह विचार मिलता है कि ऑपरेशन सफल हुआ, जबकि यह नहीं है। अपने आप से पूछें: क्या बुरा है, उपयोगकर्ता को एक सामान्य त्रुटि संदेश दिखा रहा है कि कुछ गलत हो गया है, या चुपचाप त्रुटि छोड़ रहा है और उपयोगकर्ता को लगता है कि उसका अनुरोध सफलतापूर्वक संसाधित हो रहा है? इस बारे में सोचें कि उपयोगकर्ता को कैसा लगेगा अगर उसे दो हफ्ते बाद पता चला कि उसका आदेश कभी नहीं भेजा गया था। आप शायद एक ग्राहक खो देंगे। या इससे भी बदतर, एक रोगी का MRSA पंजीकरण चुपचाप विफल रहता है, जिसके कारण मरीज को नर्सिंग द्वारा संगठित नहीं किया जाता है और जिसके परिणामस्वरूप अन्य रोगियों के प्रदूषण होते हैं, जिससे उच्च लागत या शायद मौत भी होती है।

इनमें से अधिकतर प्रकार की कोशिश-पकड़-लॉग लाइनों को हटा दिया जाना चाहिए और आपको केवल अपवाद को कॉल स्टैक को अपनाना चाहिए।

क्या आप लॉग इन नहीं करना चाहिए? आपको बिल्कुल चाहिए! लेकिन यदि आप कर सकते हैं, तो आवेदन के शीर्ष पर एक प्रयास-पकड़ ब्लॉक को परिभाषित करें। एएसपी.नेट के साथ, आप Application_Error ईवेंट को कार्यान्वित कर सकते हैं, HttpModule पंजीकृत करें या लॉगिंग करने वाले कस्टम त्रुटि पृष्ठ को परिभाषित करें। विन फॉर्म के साथ समाधान अलग है, लेकिन अवधारणा वही रहती है: एक एकल शीर्ष को पकड़ने के लिए सबसे ऊपर रखें।

कभी-कभी हालांकि, आप अभी भी एक निश्चित प्रकार के अपवाद को पकड़ना और लॉग करना चाहते हैं। एक प्रणाली जिसे मैंने अतीत में काम किया था, व्यापार परत को ValidationException एस फेंकने दें, जो प्रस्तुति परत द्वारा पकड़ा जाएगा। उन अपवादों में उपयोगकर्ता को प्रदर्शन के लिए सत्यापन जानकारी शामिल थी। चूंकि उन अपवादों को प्रेजेंटेशन लेयर में पकड़ा और संसाधित किया जाएगा, इसलिए वे एप्लिकेशन के शीर्ष भाग तक बबल नहीं होंगे और एप्लिकेशन के कैच-ऑल कोड में समाप्त नहीं होंगे। फिर भी मैं यह जानकारी लॉग इन करना चाहता था, यह पता लगाने के लिए कि उपयोगकर्ता ने कितनी बार अमान्य जानकारी दर्ज की और यह पता लगाने के लिए कि वैध कारण सही तरीके से ट्रिगर किए गए हैं या नहीं। तो यह कोई त्रुटि लॉगिंग नहीं था; बस लॉगिंग मैंने ऐसा करने के लिए निम्न कोड लिखा:

try 
{ 
    // some operations here. 
} 
catch (ValidationException ex) 
{ 
    this.logger.Log(ex); 
    throw; 
} 

परिचित लग रहा है? हां, पिछले कोड स्निपेट के समान ही दिखता है, इस अंतर के साथ कि मैंने केवल ValidationException एस पकड़ा। हालांकि, एक और अंतर था, जिसे स्निपेट को देखकर देखा नहीं जा सकता था। उस कोड में केवल एक ही स्थान था जिसमें कोड था! यह एक सजावटी था, जो मुझे अगले प्रश्न पर लाता है, आपको खुद से पूछना चाहिए:

2. क्या मैं ठोस सिद्धांतों का उल्लंघन करता हूं?

लॉगिंग, ऑडिटिंग और सुरक्षा जैसी चीजें cross-cutting concerns (या पहलुओं) कहा जाता है। उन्हें क्रॉस-कटिंग कहा जाता है, क्योंकि वे आपके आवेदन के कई हिस्सों में कटौती कर सकते हैं और अक्सर सिस्टम में कई कक्षाओं में लागू होना चाहिए। हालांकि, जब आप पाते हैं कि आप सिस्टम में कई कक्षाओं में उनके उपयोग के लिए कोड लिख रहे हैं, तो आप सबसे अधिक संभावनाओं को सॉलिड सिद्धांतों का उल्लंघन कर रहे हैं। उदाहरण के लिए निम्न उदाहरण:

public void MoveCustomer(int customerId, Address newAddress) 
{ 
    var watch = Stopwatch.StartNew(); 

    // Real operation 

    this.logger.Log("MoveCustomer executed in " + 
     watch.ElapsedMiliseconds + " ms."); 
} 

यहाँ हम समय यह MoveCustomer आपरेशन निष्पादित करने के लिए ले जाता है को मापने और हम उस जानकारी लॉग इन करें। यह बहुत संभावना है कि सिस्टम में अन्य परिचालनों को एक ही क्रॉस-कटिंग चिंता की आवश्यकता है। आप इस तरह के कोड को अपने ShipOrder, CancelOrder, CancelShipping आदि के लिए कोड जोड़ना शुरू कर देंगे, आदि विधियों के अंत में बहुत सारे कोड डुप्लिकेशन और अंततः एक रखरखाव दुःस्वप्न होता है।

यहां समस्या SOLID सिद्धांतों का उल्लंघन है। सोलिड सिद्धांत ऑब्जेक्ट ओरिएंटेड डिज़ाइन सिद्धांतों का एक सेट हैं जो लचीली और रखरखाव योग्य सॉफ्टवेयर को परिभाषित करने में आपकी सहायता करते हैं। MoveCustomer उदाहरण ने उन नियमों में से कम से कम दो नियमों का उल्लंघन किया:

  1. Single Responsibility PrincipleMoveCustomer विधि धारण करने वाली कक्षा न केवल ग्राहक को स्थानांतरित करती है, बल्कि ऑपरेशन करने में लगने वाले समय को भी मापती है। दूसरे शब्दों में इसकी कई जिम्मेदारियां हैं। आपको मापने को अपनी कक्षा में निकालना चाहिए।
  2. Open-Closed principle (ओसीपी)। सिस्टम के व्यवहार को कोड की मौजूदा लाइन को बदले बिना बदला जा सकता है। जब आपको अपवाद हैंडलिंग (तीसरी ज़िम्मेदारी) की भी आवश्यकता होती है तो आपको (12) MoveCustomer विधि को बदलना चाहिए, जो ओसीपी का उल्लंघन है।

सोल्ड सिद्धांतों का उल्लंघन करने के अलावा हमने निश्चित रूप से DRY सिद्धांत का उल्लंघन किया, जो मूल रूप से कहता है कि कोड डुप्लिकेशन खराब है, mkay।

इस समस्या का समाधान अपने ही वर्ग में प्रवेश निकालने और उस वर्ग मूल वर्ग रैप करने के लिए अनुमति देने के लिए है:

// The real thing 
public class MoveCustomerCommand 
{ 
    public virtual void MoveCustomer(int customerId, Address newAddress) 
    { 
     // Real operation 
    } 
} 

// The decorator 
public class MeasuringMoveCustomerCommandDecorator : MoveCustomerCommand 
{ 
    private readonly MoveCustomerCommand decorated; 
    private readonly ILogger logger; 

    public MeasuringMoveCustomerCommandDecorator(
     MoveCustomerCommand decorated, ILogger logger) 
    { 
     this.decorated = decorated; 
     this.logger = logger; 
    } 

    public override void MoveCustomer(int customerId, Address newAddress) 
    { 
     var watch = Stopwatch.StartNew(); 

     this.decorated.MoveCustomer(customerId, newAddress); 

     this.logger.Log("MoveCustomer executed in " + 
      watch.ElapsedMiliseconds + " ms."); 
    } 
} 

वास्तविक उदाहरण के आसपास डेकोरेटर लपेटकर करके, आप अब यह मापने में जोड़ सकते हैं वर्ग के लिए व्यवहार, प्रणाली के किसी अन्य भाग के बिना परिवर्तन के:

MoveCustomerCommand command = 
    new MeasuringMoveCustomerCommandDecorator(
     new MoveCustomerCommand(), 
     new DatabaseLogger()); 

पिछले उदाहरण हालांकि बस समस्या (केवल ठोस हिस्सा) का हिस्सा हल किया। ऊपर दिखाए गए कोड को लिखते समय, आपको सिस्टम में सभी परिचालनों के लिए सजावटी को परिभाषित करना होगा, और आप MeasuringShipOrderCommandDecorator, MeasuringCancelOrderCommandDecorator, और MeasuringCancelShippingCommandDecorator जैसे सजावटी के साथ समाप्त हो जाएंगे। यह फिर से बहुत सारे डुप्लिकेट कोड (डीआरवाई सिद्धांत का उल्लंघन) का नेतृत्व करता है, और अभी भी सिस्टम में हर ऑपरेशन के लिए कोड लिखने की आवश्यकता है। यहां क्या गुम है सिस्टम में उपयोग के मामलों पर एक आम अमूर्त है। क्या गुम है ICommandHandler<TCommand> इंटरफ़ेस है।

के इस इंटरफेस को परिभाषित करते हैं:

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

और के लिए अपने स्वयं के (Parameter Object) वर्ग MoveCustomerCommand बुलाया में MoveCustomer विधि की विधि तर्क की दुकान करते हैं:

public class MoveCustomerCommand 
{ 
    public int CustomerId { get; set; } 
    public Address NewAddress { get; set; } 
} 

और के के व्यवहार डाल देना MoveCustomer एक वर्ग में विधि जो ICommandHandler<MoveCustomerCommand> लागू करती है:

public class MoveCustomerCommandHandler : ICommandHandler<MoveCustomerCommand> 
{ 
    public void Execute(MoveCustomerCommand command) 
    { 
     int customerId = command.CustomerId; 
     var newAddress = command.NewAddress; 
     // Real operation 
    } 
} 

यह अजीब लग सकता है, लेकिन क्योंकि हम अब उपयोग के मामलों के लिए एक सामान्य अमूर्त है, हम अपने डेकोरेटर इस प्रकार पुनर्लेखन कर सकते हैं:

public class MeasuringCommandHandlerDecorator<TCommand> 
    : ICommandHandler<TCommand> 
{ 
    private ICommandHandler<TCommand> decorated; 
    private ILogger logger; 

    public MeasuringCommandHandlerDecorator(
     ICommandHandler<TCommand> decorated, ILogger logger) 
    { 
     this.decorated = decorated; 
     this.logger = logger; 
    } 

    public void Execute(TCommand command) 
    { 
     var watch = Stopwatch.StartNew(); 

     this.decorated.Execute(command); 

     this.logger.Log(typeof(TCommand).Name + " executed in " + 
      watch.ElapsedMiliseconds + " ms."); 
    } 
} 

इस नए MeasuringCommandHandlerDecorator<T>MeasuringMoveCustomerCommandDecorator की तरह दिखता है, लेकिन इस वर्ग के हो सकता है

ICommandHandler<MoveCustomerCommand> handler1 = 
    new MeasuringCommandHandlerDecorator<MoveCustomerCommand>(
     new MoveCustomerCommandHandler(), 
     new DatabaseLogger()); 

ICommandHandler<ShipOrderCommand> handler2 = 
    new MeasuringCommandHandlerDecorator<ShipOrderCommand>(
     new ShipOrderCommandHandler(), 
     new DatabaseLogger()); 

इस तरह यह व्यवस्था करने के लिए पार काटने चिंताओं को जोड़ने के लिए बहुत आसान बहुत हो जाएगा,: प्रणाली में सभी आदेश संचालकों के लिए पुन: उपयोग किया। अपने Composition Root में एक सुविधाजनक विधि बनाना आसान है जो किसी भी बनाए गए कमांड हैंडलर को सिस्टम में लागू कमांड हैंडलर के साथ लपेट सकता है। उदाहरण के लिए:

ICommandHandler<MoveCustomerCommand> handler1 = 
    Decorate(new MoveCustomerCommandHandler()); 

ICommandHandler<ShipOrderCommand> handler2 = 
    Decorate(new ShipOrderCommandHandler()); 

private static ICommandHandler<T> Decorate<T>(ICommandHandler<T> decoratee) 
{ 
    return 
     new MeasuringCommandHandlerDecorator<T>(
      new DatabaseLogger(), 
       new ValidationCommandHandlerDecorator<T>(
        new ValidationProvider(), 
        new AuthorizationCommandHandlerDecorator<T>(
         new AuthorizationChecker(
          new AspNetUserProvider()), 
         new TransactionCommandHandlerDecorator<T>(
          decoratee)))); 
} 

यदि आपका आवेदन बढ़ने लगता है, तो यह बिना किसी कंटेनर के बूटस्ट्रैप को दर्दनाक हो सकता है। विशेष रूप से जब आपके सजावटी के सामान्य प्रकार की बाधाएं होती हैं।

.NET के लिए अधिकांश आधुनिक डी कंटेनर आजकल सजावट करने वालों के लिए काफी सभ्य समर्थन रखते हैं, और विशेष रूप से ऑटोफैक (example) और सरल इंजेक्टर (example) खुले सामान्य सजावटी को पंजीकृत करना आसान बनाता है।सरल इंजेक्टर भी सजावटी को किसी दिए गए अनुमान या जटिल जेनेरिक प्रकार की बाधाओं के आधार पर सशर्त रूप से लागू करने की अनुमति देता है, सजाए गए वर्ग को injected as a factory होने की अनुमति देता है और contextual context को सजावटी में इंजेक्शन देने की अनुमति देता है, जिनमें से सभी समय-समय पर वास्तव में उपयोगी हो सकते हैं।

दूसरी तरफ एकता और महल में अवरोध सुविधाएं हैं (जैसे ऑटोफैक बीटीडब्ल्यू करता है)। सजावट सजावट के साथ बहुत आम है, लेकिन यह कवर के तहत गतिशील प्रॉक्सी पीढ़ी का उपयोग करता है। जेनेरिक सजावटी के साथ काम करने से यह अधिक लचीला हो सकता है, लेकिन जब आप रखरखाव की बात करते हैं तो आप कीमत का भुगतान करेंगे, क्योंकि आप अक्सर प्रकार की सुरक्षा खो देंगे और इंटरसेप्टर हमेशा आपको इंटरसेप्शन लाइब्रेरी पर निर्भरता लेने के लिए मजबूर करेंगे, जबकि सजावटी सुरक्षित हैं और बाहरी पुस्तकालय पर निर्भरता के बिना लिखा जा सकता है।

यदि आप अपने आवेदन को डिजाइन करने के इस तरीके के बारे में अधिक जानना चाहते हैं तो इस लेख को पढ़ें: Meanwhile... on the command side of my architecture

मुझे उम्मीद है कि इससे मदद मिलती है।

+7

ग्रेट उत्तर स्टीवन। मैं यहां जो कुछ भी कहता हूं उससे सहमत हूं, मैं लॉग अप करने और पुनर्विचार करने के लिए अपवादों को पकड़ने से भी सहमत नहीं हूं क्योंकि मुझे लगता है कि ऐसा करने के लिए ऐप डोमेन में एक केंद्रीय बिंदु होना चाहिए, हालांकि मुझे लगता है कि आप कई बार ऐसा करना चाहते हैं कार्रवाई को पुनः प्रयास करने के लिए SQLException जैसे विशिष्ट अपवाद को पकड़ें। – OutOFTouch

+1

SimpleInjector बहुत अच्छा लग रहा है और उपयोग करने के लिए मेरी सूची के शीर्ष पर है, अच्छा काम जारी रखें। यहां रुचि रखने वाले किसी भी व्यक्ति के लिए विंडसर http://mikehadlow.blogspot.com/2010/01/10-advanced-windsor-tricks-4-how-to.html के साथ सजावट का एक उदाहरण दिया गया है। – OutOFTouch

+3

@OutOFTouch: एक ऑपरेशन का पुनः प्रयास करना एओपी के लिए बहुत अच्छा फिट है, लेकिन आप इस डीबी ऑपरेशन के बारे में ऐसी चीज लपेटना नहीं चाहते हैं, लेकिन लेनदेन की सीमाओं पर। वास्तव में, [मेरा यह ब्लॉग पोस्ट] (http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91) यह दिखाता है ('DeadlockRetryCommandHandlerDecorator' पर एक नज़र डालें) । – Steven

संबंधित मुद्दे