Interception vs Injection: một quyết định kiến ​​trúc khung


28

Có khung này mà tôi đang giúp thiết kế. Có một số tác vụ phổ biến nên được thực hiện bằng cách sử dụng một số thành phần phổ biến: Ghi nhật ký, lưu đệm và nâng cao các sự kiện nói riêng.

Tôi không chắc nên sử dụng phương pháp tiêm phụ thuộc và giới thiệu tất cả các thành phần này cho từng dịch vụ (ví dụ như thuộc tính) hay tôi nên đặt một số loại dữ liệu meta cho từng phương thức dịch vụ của mình và sử dụng chức năng chặn để thực hiện các tác vụ phổ biến này ?

Đây là một ví dụ về cả hai:

Chích thuốc:

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

và đây là phiên bản khác:

Đánh chặn:

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

    }
}

Đây là câu hỏi của tôi:

  1. Giải pháp nào là tốt nhất cho một khung phức tạp?
  2. Nếu đánh chặn chiến thắng, các tùy chọn của tôi để tương tác với các giá trị bên trong của một phương thức (ví dụ để sử dụng dịch vụ bộ đệm là gì?)? Tôi có thể sử dụng các cách khác thay vì thuộc tính để thực hiện hành vi này không?
  3. Hoặc có thể có những giải pháp khác để giải quyết vấn đề?

2
Tôi không có ý kiến ​​gì về 1 và 2, nhưng liên quan đến 3: xem xét việc xem xét AoP ( lập trình hướng theo khía cạnh ) và cụ thể là vào Spring.NET .

Chỉ cần làm rõ: bạn đang tìm kiếm một so sánh giữa Dependency Injection và Aspect định hướng theo định hướng, đúng không?
M.Babcock

@ M.Babcock Không thấy bản thân mình như vậy nhưng điều đó đúng

Câu trả lời:


38

Các mối quan tâm xuyên suốt như đăng nhập, lưu trữ, vv không phải là phụ thuộc, vì vậy không nên đưa vào dịch vụ. Tuy nhiên, trong khi hầu hết mọi người sau đó dường như đạt được một khung AOP xen kẽ đầy đủ, có một mẫu thiết kế đẹp cho việc này: Trình trang trí .

Trong ví dụ trên, hãy để MyService triển khai giao diện IMyService:

public interface IMyService
{
    void DoSomething();
}

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

Điều này giữ cho lớp MyService hoàn toàn không có Mối quan tâm xuyên suốt, do đó tuân theo Nguyên tắc Trách nhiệm duy nhất (SRP).

Để áp dụng ghi nhật ký, bạn có thể thêm Trình trang trí ghi nhật ký:

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

Bạn có thể thực hiện bộ nhớ đệm, đo sáng, sự kiện, vv theo cùng một cách. Mỗi Người trang trí thực hiện chính xác một việc, vì vậy họ cũng tuân theo SRP và bạn có thể soạn chúng theo những cách phức tạp tùy ý. Ví dụ

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

5
Mẫu trang trí là một cách tuyệt vời để tách biệt các mối quan tâm đó, nhưng nếu bạn có RẤT NHIỀU dịch vụ, đó là nơi tôi sẽ sử dụng một công cụ AOP như PostSharp hoặc Castle.DocateProxy, nếu không, đối với mỗi giao diện lớp dịch vụ, tôi phải mã hóa lớp VÀ một trình trang trí logger, và mỗi trình trang trí đó có thể có khả năng là mã soạn sẵn rất giống nhau (nghĩa là bạn được cải tiến mô đun hóa / đóng gói, nhưng bạn vẫn đang lặp lại rất nhiều).
Matthew Groves

4
Đã đồng ý. Tôi đã có một cuộc nói chuyện vào năm ngoái mô tả cách chuyển từ Người trang trí sang AOP: channel9.msdn.com/Events/GOTO/GOTO-2011-Copenhagen/ Lỗi
Mark Seemann

Tôi đã mã hóa một triển khai đơn giản dựa trên chương trình nàygood.net/2015/09/08/DecoratorSpike.aspx
Dave Mateer

Làm thế nào chúng ta có thể tiêm dịch vụ và trang trí với tiêm phụ thuộc?
TIKSN

@TIKSN Câu trả lời ngắn gọn là: như hình trên . Tuy nhiên, vì bạn đang hỏi, bạn phải tìm kiếm câu trả lời cho điều gì khác, nhưng tôi không thể đoán đó là gì. Bạn có thể giải thích, hoặc có thể hỏi một câu hỏi mới ở đây trên trang web?
Mark Seemann

6

Đối với một số ít dịch vụ, tôi nghĩ rằng câu trả lời của Mark là tốt: bạn sẽ không phải tìm hiểu hoặc giới thiệu bất kỳ sự phụ thuộc nào của bên thứ 3 mới và bạn vẫn sẽ tuân thủ các nguyên tắc RẮN tốt.

Đối với một lượng lớn dịch vụ, tôi muốn giới thiệu một công cụ AOP như PostSharp hoặc Castle DynamicProxy. PostSharp có phiên bản miễn phí (như trong bia) và họ vừa mới phát hành Bộ công cụ PostSharp cho Chẩn đoán , (miễn phí như trong bia VÀ lời nói) sẽ cung cấp cho bạn một số tính năng đăng nhập ra khỏi hộp.


2

Tôi thấy việc thiết kế một khung chủ yếu là trực giao cho câu hỏi này - trước tiên bạn nên tập trung vào giao diện của khung của mình và có lẽ là một quá trình tinh thần nền tảng xem xét cách ai đó thực sự tiêu thụ nó. Bạn không muốn làm một cái gì đó ngăn nó được sử dụng theo những cách thông minh, nhưng nó chỉ nên là một đầu vào trong thiết kế khung của bạn; một trong số nhiều


1

Tôi đã phải đối mặt với vấn đề này rất nhiều lần và tôi nghĩ rằng tôi đã đưa ra một giải pháp đơn giản.

Ban đầu tôi đã đi với mẫu trang trí và thực hiện thủ công từng phương thức, khi bạn có hàng trăm phương thức thì điều này trở nên rất tẻ nhạt.

Sau đó tôi đã quyết định sử dụng PostSharp nhưng tôi không thích ý tưởng bao gồm toàn bộ thư viện chỉ để làm một cái gì đó mà tôi có thể thực hiện với (rất nhiều) mã đơn giản.

Sau đó, tôi đã đi theo con đường proxy minh bạch rất thú vị nhưng liên quan đến việc phát ra IL một cách linh hoạt trong thời gian chạy và sẽ không phải là điều mà tôi muốn làm trong môi trường sản xuất.

Gần đây tôi đã quyết định sử dụng các mẫu T4 để tự động triển khai mẫu trang trí tại thời điểm thiết kế, hóa ra các mẫu T4 thực sự khá khó để làm việc và tôi cần điều này được thực hiện nhanh chóng vì vậy tôi đã tạo ra mã dưới đây. Nó nhanh và bẩn (và nó không hỗ trợ các thuộc tính) nhưng hy vọng ai đó sẽ thấy nó hữu ích.

Đây là mã:

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

Đây là một ví dụ:

public interface ITestAdapter : IDisposable
{
    string TestMethod1();

    IEnumerable<string> TestMethod2(int a);

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

Sau đó, tạo một lớp có tên là LoggingTestAd CHƯƠNG, thực hiện ITestAd CHƯƠNG, lấy visual studio để tự động thực hiện tất cả các phương thức và sau đó chạy nó thông qua mã ở trên. Sau đó bạn nên có một cái gì đó như thế này:

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

Đây là nó với mã hỗ trợ:

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);
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.