Вопрос дизайна - разрешение круговой зависимости между объектами - PullRequest
0 голосов
/ 31 августа 2010

Я попал в круговую зависимость между двумя классами и пытаюсь найти чистое решение.

Вот основная структура:

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 должен знать о новых создаваемых элементах. Он выполняет фоновую обработку, а иногда ему требуется информация из Контейнера Предмета.

Теперь у Контейнера должен быть ResourceManager (для вызова ReportNewItem), который будет передан из ContainerManager. ResourceManager требует информацию из контейнера, которую он может получить только с помощью ContainerManager. Это создает круговую зависимость.

Я бы предпочел инициализировать объекты с интерфейсами (а не конкретные объекты), чтобы впоследствии я мог создавать фиктивные объекты для юнит-тестов (например, создавать фиктивный ResourceManager), но у меня все еще остается проблема, которую требует CM RM в своем ctor, и RM требует CM в своем ctor.

Очевидно, что это не может работать, поэтому я пытаюсь найти креативные решения. Пока у меня есть:

1) Передайте ReportNewItem контейнер, который будет использоваться, и пусть ResourceManager использует его напрямую. Это проблема, потому что ResourceManager постоянно хранит идентификаторы ItemID, о которых он знает. Это означает, что при инициализации ResourceManager, скажем, после сбоя, мне придется повторно предоставить ему все необходимые ему контейнеры.

2) Инициализировать либо CM, либо RM в две фазы: например, RM = new RM (); CM = новый CM (RM); RM.SetCM (СМ); Но это ужасно, я думаю.

3) Сделать ResourceManager членом ContainerManager. Таким образом, КМ может построить RM с "этим". Это сработает, но будет труднее во время тестирования, когда я захочу создать макет RM.

4) Инициализируйте CM с помощью IResourceManagerFactory. Пусть CM вызовет Factory.Create (this), который инициализирует RM с помощью «this», а затем сохраняет результат. Для тестирования я могу создать фиктивную фабрику, которая будет возвращать фиктивный RM. Я думаю, что это будет хорошим решением, но немного неудобно создавать фабрику только для этого.

5) Разбейте логику ResourceManager на логику, специфичную для контейнера, и создайте отдельный экземпляр в каждом контейнере. К сожалению, логика действительно кросс-контейнерная.

Я думаю, что "правильный" способ - это извлечь некоторый код в третий класс, от которого будут зависеть и CM, и RM, но я не могу придумать элегантный способ сделать это. Я придумал либо инкапсулировать логику «сообщаемых элементов», либо инкапсулировать логику информации о компонентах, ни одна из которых не кажется правильной.

Любые идеи или предложения будут с благодарностью.

Ответы [ 5 ]

2 голосов
/ 31 августа 2010

То, что вы ищете, это интерфейс.Интерфейс позволяет извлекать структуру / определение общего объекта во внешнюю ссылку, позволяя его компилировать независимо от классов Container и ResourceManager и не зависеть ни от одного из них.создайте Container, у вас будет ResourceManager, о котором вы хотите, чтобы контейнер сообщал, чтобы ... передать его конструктору или установить его как свойство.каждая из ваших циркулярных ссылок.

* Обновление *

Вам не нужно удалять все зависимости в интерфейсе ... было бы прекрасно, например, для ResourceManager знать о /зависит от Container

0 голосов
/ 01 сентября 2010

Джеймс,

Да, ComponentManager и ContainerManager - это одно и то же (имена в моем реальном коде совершенно разные, и я пытался выбрать «общие» имена для фрагментов кода - и я их запутал). Если есть какие-то другие детали, которые, по вашему мнению, могут помочь, дайте мне знать, и я предоставлю их. Я пытался сделать фрагмент кратким.

Вы правы в том, что ComponentManager напрямую не связан с отношением Component / ResourceManager. Моя проблема в том, что я хотел бы иметь возможность использовать другой ResourceManager для тестирования. Один из способов достижения этого состоит в том, чтобы CM предоставлял RM компоненту (действительно, существует только один RM, поэтому он должен быть создан кем-то другим, кроме каждого компонента).

ComponentAccessor делает не что иное, как скрытие частей Component, о которых я не хочу, чтобы ResourceManager знал (в то время как позволяет ResourceManager тестироваться с использованием ComponentAccessorMock). То же самое может быть достигнуто с помощью компонента, реализующего интерфейс, который предоставляет только те методы, которые я хочу использовать в 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 с использованием шаблона стратегии, но я не уверен, что это меня купит.

Я был бы рад услышать, что вы (или кто-либо другой, конечно) думают об этом подходе.

0 голосов
/ 31 августа 2010

Спасибо всем за ответы.

jalexiou - я посмотрю на KeyedCollection, спасибо (блин, мне действительно нужно зарегистрироваться, чтобы я мог оставлять комментарии).

Джеймс, как я уже писал, я хочу работать с интерфейсами (если ничего больше, это упрощает юнит-тестирование). Моя проблема заключается в том, что для инициализации фактического ResourceManager мне нужно будет передать ComponentManager, а для инициализации CM мне нужно будет передать RM. То, что вы предложили, это в основном двухфазная инициализация, которую я назвал решением 2. Я бы предпочел избежать такой двухфазной инициализации, но, возможно, я здесь слишком религиозен.

Филипп, я думаю, что передача 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);

Мне это кажется достаточно чистым. Надеюсь, что никто не согласен:)

Мое первоначальное возражение против передачи Компонента (или действительно что-то вроде средства доступа, которое я предложил здесь) состояло в том, что мне придется повторно предоставлять их ResourceManager после инициализации, поскольку ResourceManager постоянно хранит элементы, которые он имеет. Но, как оказалось, мне все равно придется заново инициализировать его с помощью Предметов, так что это не проблема.

Еще раз спасибо за хорошую дискуссию!

0 голосов
/ 31 августа 2010

С помощью вашего короткого фрагмента (я уверен, что это обязательное ограничение, но трудно понять, можно ли, например, ResourceManager превратить в одноэлементный).

1) Когда вызывается ReportNewItem(), вы не можете просто передать контейнер, в котором находится элемент, в ResourceManager? Таким образом, RM не нужно трогатьtainermanager.

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(), я хотел бы поместить его на фабрику для разделения причин беспокойства.

0 голосов
/ 31 августа 2010

Как насчет решения 5, но наличие контейнеров происходит от общего базового класса, который реализует упомянутую вами кросс-контейнерную логику?

...