21

I have 2 generic classes, a BaseComponent Class, and a BaseManager class.

They're both abstract and are intended to be made concrete.

public abstract class BaseManager<T> where T : BaseComponent<?>
public abstract class BaseComponent<T> where T : BaseManager<?>

BaseManager has a list of BaseComponents, which is why i want to make it generic, so a PhysicsManager : BaseManager<PhysicsComponent> would have a list of PhysicsComponents.

I want (or rather, think i need) BaseComponent to be generic because i only ever want classes derived from BaseComponent to be 'attached' to their appropriate manager. Ideally i don't want to have to write a constructor per derived component just so i can add it to a passed in concrete manager class. Ideally i want to have a constructor that takes the abstract BaseManager class.

How can i manage this kind of circular dependency?

George Duckett
  • 31,770
  • 9
  • 95
  • 162
  • I would strongly consider redesigning to avoid the circular dependency. For example, make `BaseComponent` non-generic. Have it depend on an `IManager`. Put the cast from `BaseComponent` to `TComponent` in the `BaseManager` – default.kramer Nov 15 '11 at 16:00
  • I agree it's a bit smelly as Jon points out, but i don't quite follow. If `BaseComponent` depended on an `IManager`, how would i ensure that all derived classes of `BaseComponent` had a constructor that accepted the correct concrete `IManager` implementation so i could add it to the Manager's list? If you've got the time, i'd appreciate an elaboration in an answer. – George Duckett Nov 15 '11 at 16:10

2 Answers2

30

It sounds like you might want to have two generic type parameters:

public abstract class BaseManager<TComponent, TManager>
    where TComponent : BaseComponent<TComponent, TManager>
    where TManager : BaseManager<TComponent, TManager>
public abstract class BaseComponent<TComponent, TManager>
    where TComponent : BaseComponent<TComponent, TManager>
    where TManager : BaseManager<TComponent, TManager>

Yes, it's smelly - but that's the sort of thing I've done in Protocol Buffers.

So then you'd have:

public class PhysicsManager : BaseManager<PhysicsComponent, PhysicsManager>

public class PhysicsComponent : BaseComponent<PhysicsComponent, PhysicsManager>
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
3

The loosest coupling would be if components didn't know about their managers. Here's an example of how that would work. Note that this approach requires some kind of factory mechanism if all components must be added to a manager. (Nat Pryce - "If a relationship exists between two objects, some other object should establish the relationship.")

abstract class BaseComponent
{
    public event EventHandler SomethingHappened;
}

abstract class BaseManager<TComponent> where TComponent : BaseComponent
{
    List<TComponent> components = new List<TComponent>();

    public virtual void AddComponent(TComponent component)
    {
        components.Add(component);
        component.SomethingHappened += (s, e) => OnSomethingHappened(component);
    }

    public abstract void OnSomethingHappened(TComponent component);
}

If components cannot be independent of their managers, I think it would be better that they depend on an interface defined by the need of the component. This is the Interface Segregation Principle

interface IManager
{
    void ManageMe(BaseComponent component);
}

abstract class BaseComponent
{
    public BaseComponent(IManager manager)
    {
        manager.ManageMe(this);
    }
}

abstract class BaseManager<TComponent> : IManager where TComponent : BaseComponent
{
    void IManager.ManageMe(BaseComponent component)
    {
        ManageMe((TComponent)component);
    }

    protected abstract void ManageMe(TComponent component);
}

interface IPhysicsManager : IManager
{
    void AnotherCallback(PhysicsComponent comp);
}

abstract class PhysicsComponent : BaseComponent
{
    public PhysicsComponent(IPhysicsManager manager)
        : base(manager)
    {
        manager.AnotherCallback(this);
    }
}

abstract class PhysicsManager : BaseManager<PhysicsComponent>, IPhysicsManager
{
    protected override void ManageMe(PhysicsComponent component)
    {
        throw new NotImplementedException();
    }

    public void AnotherCallback(PhysicsComponent comp)
    {
        throw new NotImplementedException();
    }
}

The downside is that the type system doesn't enforce that the correct manager is passed in, and the cast in BaseManager would then fail. I would still prefer this way and "keep the smelliness in my infrastructure" rather than have circular templates polluting all my concrete components and managers.

default.kramer
  • 5,943
  • 2
  • 32
  • 50
  • Interesting. Note that the circular dependencies can be done using pure interfaces, so they don't pollute concrete components and managers. Generic type names can be shortened with `using` directives (e.g. http://stackoverflow.com/a/161484/1429390 ). – Stéphane Gourichon Sep 07 '15 at 15:48