Я попал в круговую зависимость между двумя классами и пытаюсь найти чистое решение.
Вот основная структура:
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, но я не могу придумать элегантный способ сделать это. Я придумал либо инкапсулировать логику «сообщаемых элементов», либо инкапсулировать логику информации о компонентах, ни одна из которых не кажется правильной.
Любые идеи или предложения будут с благодарностью.