नमूने आप इसे देने से बहुत विशिष्ट होना कठिन है, लेकिन सामान्य रूप में, जब आप सबसे सेवाओं में ILogger
उदाहरणों इंजेक्षन, आप अपने आप को दो चीजों से पूछना चाहिए:
- मैं भी लॉग ऑन करें बहुत?
- क्या मैं ठोस सिद्धांतों का उल्लंघन करता हूं? इस तरह
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
उदाहरण ने उन नियमों में से कम से कम दो नियमों का उल्लंघन किया:
- Single Responsibility Principle।
MoveCustomer
विधि धारण करने वाली कक्षा न केवल ग्राहक को स्थानांतरित करती है, बल्कि ऑपरेशन करने में लगने वाले समय को भी मापती है। दूसरे शब्दों में इसकी कई जिम्मेदारियां हैं। आपको मापने को अपनी कक्षा में निकालना चाहिए।
- 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।
मुझे उम्मीद है कि इससे मदद मिलती है।
@Steven मैंने लॉगर उपयोग दिखाते हुए एक कोड नमूना जोड़ा। क्या आपको लगता है कि यह एक खराब डिजाइन है? – user1178376
क्या आप कंक्रीट कोड के साथ चित्रित कर सकते हैं, यहां तक कि एक परीक्षण भी बेहतर है, जिसे आप प्राप्त करने की कोशिश कर रहे हैं? –
खराब तरीके से मेरे प्रश्न का निर्माण करने के लिए खेद है। मैंने अपने मामले की व्याख्या करने के लिए और कोड जोड़ा। – user1178376