0

私は 2 つのクラス間の循環依存関係に陥り、クリーンな解決策を考え出そうとしています。

基本的な構造は次のとおりです。

class ContainerManager {
    Dictionary<ContainerID, Container> m_containers;

    void CreateContainer() { ... }
    void DoStuff(ContainerID containerID) { m_containers[containerID].DoStuff(); }
}

class Container {
    private Dictionary<ItemID, Item> m_items;

    void SetContainerResourceLimit(int limit) { ... }

    void DoStuff() {
        itemID = GenerateNewID();
        item = new Item();
        m_items[itemID] = item;
        // Need to call ResourceManager.ReportNewItem(itemID);
    }
}

class ResourceManager {
    private List<ItemID> m_knownItems;

    void ReportNewItem(ItemID itemID) { ... }

    void PeriodicLogic() { /* need ResourceLimit from container of each item */ }
}

ContainerManager は WCF サービスとして公開されます。これは、クライアントがアイテムとコンテナーを作成できる外部ポイントです。ResourceManager は、作成された新しいアイテムを認識する必要があります。バックグラウンド処理を行い、時にはアイテムのコンテナからの情報を必要とします。

ここで、コンテナには、ContainerManager から渡される ResourceManager (ReportNewItem を呼び出すため) が必要です。ResourceManager は、ContainerManager を使用してのみ取得できるコンテナからの情報を必要とします。これにより、循環依存が作成されます。

後で単体テスト用のモック オブジェクトを作成できるように (具体的なオブジェクトではなく)、インターフェイスを使用してオブジェクトを初期化することをお勧めします (たとえば、モック ResourceManager を作成します)。 RM はその ctor に CM を必要とします。

明らかにこれではうまくいかないので、クリエイティブな解決策を考え出そうとしています。これまでのところ、私は持っています:

1) ReportNewItem に使用するコンテナを渡し、ResourceManager に直接使用させる。ResourceManager は認識している ItemID を永続的に保存するため、これは面倒です。これは、たとえばクラッシュ後に ResourceManager を初期化するときに、必要なすべてのコンテナーを再提供する必要があることを意味します。

2) CM または RM のいずれかを 2 つのフェーズで初期化します。例: RM = new RM(); CM = 新しい CM(RM); RM.SetCM(CM); しかし、これは醜いと思います。

3) ResourceManager を ContainerManager のメンバーにします。したがって、CM は「this」を使用して RM を構築できます。これは機能しますが、テスト中に RM モックを作成するときに苦労します。

4) IResourceManagerFactory で CM を初期化します。「this」を使用して RM を初期化する Factory.Create(this) を CM に呼び出してもらい、結果を保存します。テストのために、モック RM を返すモック ファクトリを作成できます。これは良い解決策になると思いますが、このためだけにファクトリを作成するのは少し面倒です。

5) ResourceManager ロジックをコンテナ固有のロジックに分割し、各コンテナに個別のインスタンスを作成します。残念ながら、ロジックは実際にはコンテナ間です。

「正しい」方法は、CM と RM の両方が依存する第 3 のクラスにコードを引き出すことだと思いますが、それを行うエレガントな方法を思いつくことはできません。「報告された項目」のロジックをカプセル化するか、コンポーネント情報ロジックをカプセル化するかのどちらかを思いつきましたが、どちらも正しくないようです。

洞察や提案をいただければ幸いです。

4

5 に答える 5

2

あなたが探しているのはインターフェースです。インターフェイスを使用すると、共有オブジェクトの構造/定義を外部参照に抽出して、ContainerResourceManagerクラスの両方から独立してコンパイルし、どちらにも依存しないようにすることができます。

を作成すると、コンテナが報告する必要がある...コンストラクタに渡すか、プロパティとして設定しますContainerResourceManager

public interface IResourceManager {
    void ReportNewItem(ItemID itemID);
    void PeriodicLogic();
}


public class Container {
    private Dictionary<ItemID, Item> m_items;

    //  Reference to the resource manager, set by constructor, property, etc.
    IResourceManager resourceManager;

    public void SetResourceManager (IResourceManager ResourceManager) {
        resourceManager = ResourceManager;
    }

    public void DoStuff() {
        itemID = GenerateNewID();
        item = new Item();
        m_items[itemID] = item;
        resourceManager.ReportNewItem(itemID);
    }
}


public class ResourceManager : IResourceManager {
    private List<ItemID> m_knownItems;

    public void ReportNewItem(ItemID itemID) { ... }
    public void PeriodicLogic() { ... }
}


//  use it as such:
IResourceManager rm = ResourceManager.CreateResourceManager(); // or however
Container container = new Container();
container.SetResourceManager(rm);
container.DoStuff();

この概念を各循環参照に拡張します。


* アップデート *

インターフェイスへのすべての依存関係を削除する必要はありません...たとえば、 a が aResourceManagerについて知っている/依存している場合は、完全に問題ありません。Container

于 2010-08-30T20:49:59.953 に答える
0

解決策 5 についてはどうでしょうか。ただし、コンテナーは、あなたが言及したクロスコンテナー ロジックを実装する共通の基本クラスから派生させますか?

于 2010-08-30T20:48:23.710 に答える
0

あなたの短いスニペットだけで(必要な制約は確かですが、たとえば ResourceManager をシングルトンに変えることができるかどうかを知るのは難しいです。)ここに私の簡単な考えがあります

1)ReportNewItem()が呼び出されたときに、アイテムが入っているコンテナを ResourceManager に渡すことはできませんか? このように、RM は containermanager に触れる必要はありません。

class Container {
    private IResourceManager m_rm; //.. set in constructor injection or property setter

    void DoStuff() {
        itemID = GenerateNewID();
        item = new Item();
        m_items[itemID] = item;
        m_rm.ReportNewItem(this, itemId);
    }
}

class ResourceManager {
    private List<ItemID> m_knownItems;
    private Dictionary<ItemID, Container> m_containerLookup;        

    void ReportNewItem(Container, ItemID itemID) { ... }

    void PeriodicLogic() { /* need ResourceLimit from container of each item */ }
}

2) 私は工場​​のファンです。一般に、クラスの正しいインスタンスを構築または取得することが単にnew().

于 2010-08-30T20:57:09.937 に答える
0

ジェームズ、

はい、ComponentManager と ContainerManager はまったく同じです (私の実際のコードの名前はまったく異なり、コード スニペットに「一般的な」名前を選択しようとしていましたが、混乱してしまいました)。他に役立つと思われる詳細がありましたら、お知らせください。提供させていただきます。スニペットを簡潔にしようとしていました。

ComponentManager が Component/ResourceManager 関係に直接関与していないことは正しいです。私の問題は、テストに別の ResourceManager を使用できるようにしたいということです。これを実現する 1 つの方法は、CM に RM をコンポーネントに提供させることです (実際、RM は 1 つしかないため、各コンポーネント以外の誰かが作成する必要があります)。

ComponentAccessor は、ComponentAccessorMock を使用して ResourceManager をテストできるようにしながら、ResourceManager に知らせたくない Component の部分を非表示にする以外にはほとんど何もしません。RM に使用させたいメソッドだけを公開するインターフェイスをコンポーネントに実装させることで、同じことが実現できます。これは実際に私が自分のコードで行ったことであり、「expose Component.GetMaxResource」によって参照されたものであると思われます。

コードは大まかに次のようになります。

// Initialization:

RM = new RM();
CM = new CM(RM);   // saves RM as a member

//
// Implementation
//

// ComponentManager.CreateComponent
C = new Component(m_RM);  // saves RM as a member

// Component.CreateNewItem
{
    Item item = new Item();
    m_RM.ReportNewItem(this, item);
}

また、ReportNewItem は、必要なメソッドを公開するインターフェイスを想定しています。これは私にはかなりきれいに思えます。

考えられる代替手段は、戦略パターンを使用して ResourceManager をカスタマイズ可能にすることですが、それで何が得られるかはわかりません。

あなた (またはもちろん他の誰か) がこのアプローチについてどう思うかを聞いてうれしいです.

于 2010-09-01T11:56:48.363 に答える
0

回答ありがとうございます。

jalexiou - KeyedCollection を調べてみます。ありがとうございます (おいおい、コメントを投稿できるように登録する必要があります)。

ジェームズ、私が書いたように、私はインターフェイスを扱いたいと思っています (他に何もないとしても、ユニットテストを簡素化します)。私の問題は、実際の ResourceManager を初期化するには ComponentManager を渡す必要があり、CM を初期化するには RM を渡す必要があることです。あなたが提案したのは、基本的に私がソリューション 2 と呼んだ 2 段階の初期化です。

Philip さん、Component を ReportNewItem に渡すと、ResourceManager に公開しすぎることになると思います (Component はさまざまな操作をサポートしているため、ResourceManager にはアクセスできません)。

しかし、もう一度考えてみると、次のアプローチを取ることができます。

class ComponentManager { ... }

class Component {
    private ComponentAccessorForResource m_accessor;
    private ResourceManager m_rm;

    Component(ResourceManager rm) {
        m_accessor = new ComponentAccessorForResource(this);
        m_rm = rm;
    }
    void DoStuff() {
        Item item = CreateItem();
        ResourceManager.ReportNewItem(item.ID, m_accessor);
    }
    int GetMaxResource() { ... }
 }

 class ComponentAccessorForResource {
     private Component m_component;
     ComponentAccessorForResource(Component c) { m_component = c; }
     int GetMaxResource() { return m_component.GetMaxResource(); }
 }

 ResourceManager rm = new ResourceManager();
 ComponentManager cm = new ComponentManager(rm);

これは私には十分にきれいに思えます。誰も同意しないことを願っています:)

Component (または実際にここで提案したアクセサーのようなもの) を渡すことに対する最初の反対意見は、ResourceManager が保持する Item を永続的に保存するため、初期化時に ResourceManager にそれらを再提供する必要があるというものでした。しかし、結局のところ、アイテムで再初期化する必要があるため、問題はありません。

良い議論をありがとう!

于 2010-08-31T04:58:41.117 に答える