5

私は、依存性注入などの優れた機能を使用してアプリケーションを管理しやすくするために、アプリケーションのリファクタリングに取り組んできました。これを行うとき、私は循環依存に複数回遭遇しました。

したがって、これが周期的依存性の典型的な例です。

interface IA
{
    int Data { get; }
}
interface IBefore
{
    void DoStuffBefore();
}
class A: IA
{
    public int Data { get; private set; }
    IBefore before;
    public A(IBefore before)
    {
        this.before = before;
    }
    public void Increment()
    {
        before.DoStuffBefore();
        Data++;
    }
}
class B: IBefore
{
    IA a;
    public B(IA a)
    {
        this.a = a;
    }
    public void WriteADataToConsole()
    {
        Console.Write(a.Data);
    }
    public void DoStuffBefore() //From IBefore
    {
        WriteADataToConsole();
    }
}

お互いに必要なので、どちらのクラスも作成できません。ここで、この場合に行う標準(?)は、AのデータをAから分離することです。

public interface IA
{
    int Data { get; set; }
}
public interface IBefore
{
    void DoStuffBefore();
}
class AData : IA
{
    public int Data { get; set; }
}
class A
{
    public IA Data { get; private set; }
    IBefore before;
    public A(IA data, IBefore before)
    {
        this.Data = data;
        this.before = before;
    }
    public void Increment()
    {
        before.DoStuffBefore();
        Data.Data++;
    }
}
class B : IBefore
{
    IA a;
    public B(IA a)
    {
        this.a = a;
    }
    public void WriteADataToConsole()
    {
        Console.Write(a.Data);
    }
    public void DoStuffBefore() //From IBefore
    {
        WriteADataToConsole();
    }
}

上記は循環依存性を解決します。これは、最初にADataを作成してから、それをBに挿入し、BをAに挿入できるためです。ただし、BがリッスンできるイベントiIAを配置することもできます。

public interface IA
{
    int Data { get; }
    event Action BeforeEvent;
}

class A: IA
{
    public int Data { get; private set; }
    public event Action BeforeEvent;
    public void Increment()
    {
        BeforeEvent();
        Data++;
    }
}

class B
{
    IA a;
    public B(IA a)
    {
        this.a = a;
        a.BeforeEvent += new Action(WriteADataToConsole);
    }
    void WriteADataToConsole() //Event listener
    {
        Console.Write(a.Data);
    }
}

イベントアプローチを依存性注入に変換しようとしていたので、これは私が偶然見つけたものです。そうすることで、循環依存を取得したことに気づきました。

私の脳を悩ませている質問のいくつかは次のとおりです。

  • どちらのソリューションも循環依存関係を解決し(右?)、私が見る限り、Aを同程度に拡張できるようになっていますが、どちらが最良の設計と見なされますか?
  • いつイベントを使用するか、いつDIを使用して循環依存関係を解決するか、および一般的には、どのようなガイドラインがありますか?
  • 明らかに、AがBからの戻り値を必要とする場合、イベントは適切ではありません。つまり、voidが返される場合、イベントは常に優先されるということですか?
  • 各ソリューションの長所と短所は何ですか?
4

1 に答える 1

3

良い質問!95% のケースでは、この 2 つのエンティティをマージするか、別の方法で依存関係を解消する必要がありますが、何らかの理由でエンティティを 1 つにマージできない場合はどうすればよいでしょうか (UI の操作は非常に難しい場合があります)。Mark Seemann による "Dependency Injection in .NET"に関する本があり、循環的な依存関係を解消する 2 つのアプローチが説明されています。

  • イベント— DI ブックによると望ましい方法であり、あなたはすでにこれを行っています。それは私には問題ないようです
  • プロパティの注入—コンストラクターの注入とは対照的に、プロパティの注入は、注入されるリソースがオプションであることを意味します。

プロパティを使用した 2 番目の実装には、コンストラクターがありますpublic A(IA data, IBefore before) IA dataとの両方IBefore beforeが依存性注入の観点から必要です— ここが cicle を破る最良のポイントです! オプションの実装は次のとおりですIBefore

class A
{
    public IA Data { get; private set; }
    public IBefore Before { get; set; }

    public A(IA data)
    {
        this.Data = data;
    }
    public void Increment()
    {
        // here should be design decision: if Before is optional…
        if(Before == null)
        {
            Before.DoStuffBefore();
        }    

        // …or required
        if(Before == null)
        {
            throw new Exception("'Before' is required");
        }

        Data.Data++;
    }
}

Before.DoStuffBefore()オプションの場合は呼び出しをスキップするか、Before必要な場合は例外を発生させるかはあなた次第です

あなたの質問によると:

  • 最高のデザインと見なされるのはどれですか? いくつかのガイドラインは何ですか?長所と短所- どちらも問題ありません。イベントはより一般的です。プロパティは、実装と処理がより簡単です
  • void が返された場合、イベントは常に優先されますか? — はい、私にとっては
于 2012-12-17T18:44:31.120 に答える