インターセプトとインジェクション:フレームワークアーキテクチャの決定


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.バブコック

@ M.Babcock自分ではそのように見ていませんが、それは正しいです

回答:


38

ロギング、キャッシングなどの横断的関心事は依存関係ではないため、サービスに注入すべきではありません。ほとんどの人は、その後、完全なインターリーブAOPフレームワークのために達するように見えるながらしかし、このための素敵なデザインパターンがあります:デコレーター

上記の例では、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");
    }
}

キャッシング、メータリング、イベンティングなどを同じ方法で実装できます。各デコレータは1つのことを行うため、SRPにも準拠し、任意の複雑な方法で構成できます。例えば

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

5
デコレータパターンはこれらの懸念を分離する素晴らしい方法ですが、サービスがたくさんある場合は、PostSharpやCastle.DynamicProxyなどのAOPツールを使用します。そうでない場合は、サービスクラスインターフェイスごとに、クラスをコーディングする必要がありますロガーデコレータ、およびこれらの各デコレータは、非常によく似たボイラープレートコードになる可能性があります(つまり、モジュール化/カプセル化が改善されますが、それでも多くのことを繰り返しています)。
マシューグローブス

4
同意した。私はAOPにデコレータから移動する方法について説明し、昨年講演を行いました:channel9.msdn.com/Events/GOTO/GOTO-2011-Copenhagen/...
マーク・シーマン


依存性注入でサービスとデコレータを注入するにはどうすればよいですか?
TIKSN

@TIKSN短い答えは、上記のとおりです。しかし、あなたが尋ねているので、あなたは何か他のものへの答えを探しているに違いありませんが、私はそれが何であるか推測できません。このサイトで詳しく説明してもらえますか、それとも新しい質問をここでお願いできますか?
マークゼーマン

6

少数のサービスについては、Markの答えは良いと思います。新しいサードパーティの依存関係を学習したり導入したりする必要はなく、引き続き優れた原則に従うことになります。

大量のサービスには、PostSharpやCastle DynamicProxyなどのAOPツールをお勧めします。PostSharpには無料の(ビールのように)バージョンがあり、最近診断用のPostSharp Toolkitをリリースしました(ビールおよび音声のように無料)。


2

フレームワークの設計は、この質問にほぼ直交していることがわかります。最初にフレームワークのインターフェイスに焦点を当てる必要があります。おそらく、バックグラウンドの精神プロセスとして、誰かが実際にそれを消費する方法を検討してください。あなたはそれが巧妙な方法で使用されることを妨げる何かをしたくありませんが、それはあなたのフレームワーク設計への入力に過ぎないはずです。多くの中の一つ。


1

私はこの問題に何度も直面しましたが、簡単な解決策を思いついたと思います。

最初にデコレータパターンを使用して、各メソッドを手動で実装しましたが、何百ものメソッドがある場合、これは非常に退屈になります。

次に、PostSharpを使用することにしましたが、(多くの)単純なコードで達成できることを行うためだけにライブラリ全体を含めるという考えは好きではありませんでした。

その後、透過的プロキシルートを下って行きましたが、これは楽しいですが、実行時に動的にILを発行する必要があり、本番環境ではやりたいことではありませんでした。

私は最近、設計時にT4テンプレートを使用してデコレータパターンを自動的に実装することを決定しました。それは速くて汚いです(そしてプロパティをサポートしません)が、誰かがそれを役に立つと思うことを願っています。

コードは次のとおりです。

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

次にITestAdapterを実装するLoggingTestAdapterというクラスを作成し、Visual Studioですべてのメソッドを自動実装してから、上記のコードを実行します。次のようなものが必要です。

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);
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.