Durdurma vs Enjeksiyon: Bir çerçeve mimarisi kararı


28

Tasarıma yardım ettiğim bu çerçeve var. Bazı ortak bileşenler kullanılarak yapılması gereken bazı ortak görevler vardır: Özellikle olayları günlüğe kaydetme, önbelleğe alma ve yükseltme.

Bağımlılık enjeksiyonunu kullanmanın ve tüm bu bileşenlerin her bir servise (örneğin özellikler olarak) sunulmasının daha iyi olup olmadığından emin değilim veya hizmetlerimin her yöntemi üzerine yerleştirilmiş bir tür meta verim var mı ve bu ortak görevleri yapmak için müdahaleyi kullanmalı mıyım? ?

İşte ikisine de bir örnek:

Enjeksiyon:

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);
    }
}

ve işte diğer sürüm:

Kesişme:

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

    }
}

İşte benim sorularım:

  1. Karmaşık bir çerçeve için hangi çözüm en iyisidir?
  2. Durdurma kazanırsa, bir yöntemin dahili değerleri ile etkileşim kurma seçeneklerim nelerdir (örneğin önbellek servisi ile kullanmak için?)? Bu davranışı uygulamak için nitelikler yerine başka yollar kullanabilir miyim?
  3. Ya da belki sorunu çözmek için başka çözümler olabilir?

2
1 ve 2 ile ilgili bir fikrim yok, ama 3 ile ilgili: AoP'ye ( Yönelimli programlama ) ve özellikle Spring.NET'e bakmayı düşünün .

Sadece açıklığa kavuşturmak için: Bağımlılık Enjeksiyonu ile Açı Yönelimli Programlama arasında bir karşılaştırma mı arıyorsunuz?
M.Babcock

@ M.Babcock Kendim bu şekilde görmedim ama bu doğru

Yanıtlar:


38

Kesişme Günlüğe kaydetme, önbellekleme vb. Gibi endişeler bağımlılık değildir, bu yüzden hizmetlere enjekte edilmemelidir. Bununla birlikte, çoğu insan tam serpiştirici AOP çerçevesine ulaşıyor gibi gözükse de, bunun için hoş bir tasarım deseni var: Dekoratör .

Yukarıdaki örnekte, MyService'in IMyService arabirimini uygulamasına izin verin:

public interface IMyService
{
    void DoSomething();
}

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

Bu, MyService sınıfını tamamen Kesintisiz Endişelerden uzak tutar, böylece Tek Sorumluluk İlkesini (SRP) izler.

Günlük uygulamak için, bir günlük Dekoratör ekleyebilirsiniz:

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");
    }
}

Önbelleğe alma, ölçme, olay vb. İşlemleri aynı şekilde yapabilirsiniz. Her bir Dekoratör tam olarak bir şey yapar, böylece SRP'yi de takip ederler ve bunları keyfi bir şekilde karmaşık şekillerde oluşturabilirsiniz. Örneğin

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

5
Dekoratör deseni bu endişeleri ayrı tutmanın harika bir yoludur, ancak çok sayıda hizmetiniz varsa, PostSharp veya Castle.DynamicProxy gibi bir AOP aracını kullanırdım, aksi halde her servis sınıfı arayüzü için sınıfı kodlamam gerekir. VE bir logger dekoratörü ve bu dekoratörlerin her biri potansiyel olarak çok benzer bir kazan plakası kodu olabilir (yani, gelişmiş modülerleştirme / kapsülleme elde edersiniz, ancak yine de kendiniz için çok fazla şey yapıyorsunuzdur).
Matthew Groves

4
Kabul. Geçen yıl Dekoratörlerden AOP'a
Mark Seemann


Bağımlılık enjeksiyonuyla servis ve dekoratörlere nasıl enjeksiyon yapabiliriz?
TIKSN

@TIKSN Kısa cevap: yukarıda gösterildiği gibi . Bununla birlikte, sorduğunuzdan beri, başka bir şeye bir cevap aramalısınız, ancak bunun ne olduğunu tahmin edemiyorum. Sitede detaylandırılabilir veya yeni bir soru sorabilir misiniz?
Mark Seemann

6

Bir avuç hizmet için Mark'ın cevabının iyi olduğunu düşünüyorum: 3. parti bağımlılıklarını öğrenmek veya tanıtmak zorunda kalmayacaksınız ve yine de iyi SOLID ilkelerini izliyor olacaksınız.

Çok sayıda hizmet için PostSharp veya Castle DynamicProxy gibi bir AOP aracı öneririm. PostSharp'ın (biradaki gibi) ücretsiz bir sürümü var ve yakın zamanda Tanılama için PostSharp Araç Kiti'ni (bira ve konuşmada olduğu gibi) piyasaya sürdüler .


2

Bir çerçevenin tasarımını bu soruya büyük ölçüde dik olacak şekilde buluyorum - önce çerçevenizin arayüzüne odaklanmalısınız ve belki de arka plandaki zihinsel süreç olarak birinin gerçekten onu nasıl tüketebileceğini düşünmelisiniz. Akıllıca kullanılmasını önleyen bir şey yapmak istemezsiniz , ancak yalnızca çerçeve tasarımınıza bir girdi olmalıdır; birçoğu arasında.


1

Bu sorunla pek çok kez karşılaştım ve sanırım basit bir çözüm buldum.

Başlangıçta dekoratör paternine gittim ve her metodu elle uyguladım, yüzlerce metot varken bu çok sıkıcı oluyor.

Daha sonra PostSharp'ı kullanmaya karar verdim ancak basit bir kodla başarabileceğim bir şeyi yapmak için kütüphanenin tamamını dahil etme fikrini beğenmedim.

Daha sonra eğlenceli olan ancak çalışma zamanında IL'yi dinamik olarak yayan ve prodüksiyon ortamında yapmak istediğim bir şey olmayacak olan şeffaf proxy yolundan indim.

Geçenlerde dekoratör kalıbını tasarım zamanında otomatik olarak uygulamak için T4 şablonlarını kullanmaya karar verdim, sonuçta T4 şablonlarının çalışmasının oldukça zor olduğu ortaya çıktı ve hızlıca bu işlemi yapmam gerekiyordu. Çabuk ve kirli (ve özellikleri desteklemiyor) ancak umarım birileri onu yararlı bulacaktır.

İşte kod:

        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";

İşte bir örnek:

public interface ITestAdapter : IDisposable
{
    string TestMethod1();

    IEnumerable<string> TestMethod2(int a);

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

Ardından, ITestAdapter'ı uygulayan LoggingTestAdapter adlı bir sınıf oluşturun, tüm yöntemleri otomatik olarak uygulamak için görsel stüdyosu alın ve ardından yukarıdaki kodda çalıştırın. Öyleyse böyle bir şeye sahip olmalısınız:

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);
        }
    }
}

Bu destek koduyla birlikte:

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);
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.