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.