अवरोधन बनाम इंजेक्शन: एक रूपरेखा वास्तुकला निर्णय


28

यह ढांचा है जिसे मैं डिजाइन करने में मदद कर रहा हूं। कुछ सामान्य कार्य हैं जिन्हें कुछ सामान्य घटकों का उपयोग करके किया जाना चाहिए: लॉगिंग, कैशिंग और विशेष रूप से घटनाओं को उठाना।

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

यहाँ दोनों का एक उदाहरण दिया गया है:

इंजेक्शन:

public class MyService
{
    public ILoggingService Logger { get; set; }

    public IEventBroker EventBroker { get; set; }

    public ICacheService Cache { get; set; }

    public void DoSomething()
    {
        Logger.Log(myMessage);
        EventBroker.Publish<EventType>();
        Cache.Add(myObject);
    }
}

और यहाँ अन्य संस्करण है:

अवरोधन:

public class MyService
{
    [Log("My message")]
    [PublishEvent(typeof(EventType))]
    public void DoSomething()
    {

    }
}

यहाँ मेरे सवाल हैं:

  1. जटिल ढांचे के लिए कौन सा समाधान सबसे अच्छा है?
  2. यदि अवरोधन जीतता है, तो एक विधि के आंतरिक मूल्यों के साथ बातचीत करने के लिए मेरे विकल्प क्या हैं (उदाहरण के लिए कैश सेवा के साथ उपयोग करने के लिए?)। क्या मैं इस व्यवहार को लागू करने के लिए विशेषताओं के बजाय अन्य तरीकों का उपयोग कर सकता हूं?
  3. या शायद समस्या को हल करने के लिए अन्य समाधान हो सकते हैं?

2
मेरी 1 और 2 पर एक राय नहीं है, लेकिन 3 के बारे में: AoP ( पहलू-उन्मुख प्रोग्रामिंग ) और विशेष रूप से Spring.NET में देखने पर विचार करें

बस स्पष्ट करने के लिए: आप निर्भरता इंजेक्शन और पहलू ओरिएंटेड प्रोग्रामिंग के बीच तुलना के लिए देख रहे हैं, सही?
M.Babcock

@ M.Babcock ने इसे खुद इस तरह से नहीं देखा, लेकिन यह सही है

जवाबों:


38

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

उपरोक्त उदाहरण में, MyService IMyService इंटरफ़ेस लागू करें:

public interface IMyService
{
    void DoSomething();
}

public class MyService : IMyService
{
    public void DoSomething()
    {
        // Implementation goes here...
    }
}

यह MyService वर्ग को पूरी तरह से क्रॉस-कटिंग कंसर्न से मुक्त रखता है, इस प्रकार सिंगल रिस्पॉन्सिबिलिटी प्रिंसिपल (SRP) का अनुसरण करता है ।

लॉगिंग लागू करने के लिए, आप एक लॉगिंग डेकोरेटर जोड़ सकते हैं:

public class MyLogger : IMyService
{
    private readonly IMyService myService;
    private readonly ILoggingService logger;

    public MyLogger(IMyService myService, ILoggingService logger)
    {
        this.myService = myService;
        this.logger = logger;
    }

    public void DoSomething()
    {
        this.myService.DoSomething();
        this.logger.Log("something");
    }
}

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

var service = new MyLogger(
    new LoggingService(),
    new CachingService(
        new Cache(),
        new MyService());

5
डेकोरेटर पैटर्न उन चिंताओं को अलग रखने का एक शानदार तरीका है, लेकिन अगर आपके पास बहुत सारी सेवाएँ हैं, तो मैं जहाँ AOP टूल का उपयोग करूँगा जैसे PostSharp या Castle.DynamicProxy, अन्यथा प्रत्येक सेवा वर्ग इंटरफ़ेस के लिए, मुझे कक्षा को कोड करना होगा और एक लकड़हारा डेकोरेटर, और उनमें से प्रत्येक डेकोरेटर संभावित रूप से बहुत समान बॉयलरप्लेट कोड हो सकता है (यानी आपको बेहतर मॉडर्नाइजेशन / इनकैप्सुलेशन मिलता है, लेकिन आप अभी भी अपने आप को बहुत दोहरा रहे हैं)।
मैथ्यू ग्रोव्स

4
माना। मैंने पिछले साल एक बात दी थी जिसमें बताया गया था कि डेकोरेटर्स से एओपी तक कैसे पहुंचा जाए: channel9.msdn.com/Events/GOTO/GOTO-2011-Copenhagen/…
मार्क सेमन


हम निर्भरता इंजेक्शन के साथ सेवा और सज्जाकार कैसे इंजेक्ट कर सकते हैं?
TIKSN

@TIKSN संक्षिप्त उत्तर है: जैसा कि ऊपर दिखाया गया है । जब से आप पूछ रहे हैं, हालांकि, आप किसी और चीज़ का जवाब मांग रहे होंगे, लेकिन मैं अनुमान नहीं लगा सकता कि वह क्या है। क्या आप यहाँ साइट पर एक नया प्रश्न पूछ सकते हैं, या विस्तृत कर सकते हैं?
मार्क सेमैन जूल

6

मुट्ठी भर सेवाओं के लिए, मुझे लगता है कि मार्क का उत्तर अच्छा है: आपको किसी भी नए तृतीय पक्ष निर्भरता को सीखना या पेश नहीं करना होगा और आप अभी भी अच्छे एसओएलआईडी सिद्धांतों का पालन करेंगे।

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


2

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


1

मैंने बहुत बार इस समस्या का सामना किया है और मुझे लगता है कि मैं एक सरल समाधान के साथ आया हूं।

प्रारंभ में मैं डेकोरेटर पैटर्न के साथ गया और मैन्युअल रूप से प्रत्येक विधि को लागू किया, जब आपके पास सैकड़ों विधियां हैं तो यह बहुत थकाऊ हो जाता है।

मैंने तब PostSharp का उपयोग करने का निर्णय लिया, लेकिन मुझे ऐसा कुछ करने के लिए एक संपूर्ण पुस्तकालय शामिल करने का विचार पसंद नहीं आया, जिसे मैं (बहुत सारे) सरल कोड के साथ पूरा कर सकूं।

मैं फिर पारदर्शी प्रॉक्सी मार्ग पर चला गया जो मज़ेदार था लेकिन इसमें गतिशील रूप से आईएल को चलाने के समय शामिल किया गया था और ऐसा कुछ नहीं होगा जो मैं उत्पादन वातावरण में करना चाहता हूं।

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

यहाँ कोड है:

        var linesToUse = code.Split(Environment.NewLine.ToCharArray()).Where(l => !string.IsNullOrWhiteSpace(l));
        string classLine = linesToUse.First();

        // Remove the first line this is just the class declaration, also remove its closing brace
        linesToUse = linesToUse.Skip(1).Take(linesToUse.Count() - 2);
        code = string.Join(Environment.NewLine, linesToUse).Trim()
            .TrimStart("{".ToCharArray()); // Depending on the formatting this may be left over from removing the class

        code = Regex.Replace(
            code,
            @"public\s+?(?'Type'[\w<>]+?)\s(?'Name'\w+?)\s*\((?'Args'[^\)]*?)\)\s*?\{\s*?(throw new NotImplementedException\(\);)",
            new MatchEvaluator(
                match =>
                    {
                        string start = string.Format(
                            "public {0} {1}({2})\r\n{{",
                            match.Groups["Type"].Value,
                            match.Groups["Name"].Value,
                            match.Groups["Args"].Value);

                        var args =
                            match.Groups["Args"].Value.Split(",".ToCharArray())
                                .Select(s => s.Trim().Split(" ".ToCharArray()))
                                .ToDictionary(s => s.Last(), s => s.First());

                        string call = "_decorated." + match.Groups["Name"].Value + "(" + string.Join(",", args.Keys) + ");";
                        if (match.Groups["Type"].Value != "void")
                        {
                            call = "return " + call;
                        }

                        string argsStr = args.Keys.Any(s => s.Length > 0) ? ("," + string.Join(",", args.Keys)) : string.Empty;
                        string loggedCall = string.Format(
                            "using (BuildLogger(\"{0}\"{1})){{\r\n{2}\r\n}}",
                            match.Groups["Name"].Value,
                            argsStr,
                            call);
                        return start + "\r\n" + loggedCall;
                    }));
        code = classLine.Trim().TrimEnd("{".ToCharArray()) + "\n{\n" + code + "\n}\n";

यहाँ एक उदाहरण है:

public interface ITestAdapter : IDisposable
{
    string TestMethod1();

    IEnumerable<string> TestMethod2(int a);

    void TestMethod3(List<string[]>  a, Object b);
}

फिर LoggingTestAdapter नामक एक क्लास बनाएं, जो ITestAdapter को कार्यान्वित करता है, सभी तरीकों को लागू करने के लिए विज़ुअल स्टूडियो को ऑटो में लाएं और फिर इसे ऊपर दिए गए कोड के माध्यम से चलाएं। आपको तब कुछ इस तरह से होना चाहिए:

public class LoggingTestAdapter : ITestAdapter
{

    public void Dispose()
    {
        using (BuildLogger("Dispose"))
        {
            _decorated.Dispose();
        }
    }
    public string TestMethod1()
    {
        using (BuildLogger("TestMethod1"))
        {
            return _decorated.TestMethod1();
        }
    }
    public IEnumerable<string> TestMethod2(int a)
    {
        using (BuildLogger("TestMethod2", a))
        {
            return _decorated.TestMethod2(a);
        }
    }
    public void TestMethod3(List<string[]> a, object b)
    {
        using (BuildLogger("TestMethod3", a, b))
        {
            _decorated.TestMethod3(a, b);
        }
    }
}

यह समर्थन कोड के साथ है:

public class DebugLogger : ILogger
{
    private Stopwatch _stopwatch;
    public DebugLogger()
    {
        _stopwatch = new Stopwatch();
        _stopwatch.Start();
    }
    public void Dispose()
    {
        _stopwatch.Stop();
        string argsStr = string.Empty;
        if (Args.FirstOrDefault() != null)
        {
            argsStr = string.Join(",",Args.Select(a => (a ?? (object)"null").ToString()));
        }

        System.Diagnostics.Debug.WriteLine(string.Format("{0}({1}) @ {2}ms", Name, argsStr, _stopwatch.ElapsedMilliseconds));
    }

    public string Name { get; set; }

    public object[] Args { get; set; }
}

public interface ILogger : IDisposable
{
    string Name { get; set; }
    object[] Args { get; set; }
}


public class LoggingTestAdapter<TLogger> : ITestAdapter where TLogger : ILogger,new()
{
    private readonly ITestAdapter _decorated;

    public LoggingTestAdapter(ITestAdapter toDecorate)
    {
        _decorated = toDecorate;
    }

    private ILogger BuildLogger(string name, params object[] args)
    {
        return new TLogger { Name = name, Args = args };
    }

    public void Dispose()
    {
        _decorated.Dispose();
    }

    public string TestMethod1()
    {
        using (BuildLogger("TestMethod1"))
        {
            return _decorated.TestMethod1();
        }
    }
    public IEnumerable<string> TestMethod2(int a)
    {
        using (BuildLogger("TestMethod2", a))
        {
            return _decorated.TestMethod2(a);
        }
    }
    public void TestMethod3(List<string[]> a, object b)
    {
        using (BuildLogger("TestMethod3", a, b))
        {
            _decorated.TestMethod3(a, b);
        }
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.