circular generic type parameters

前端 未结 2 980
时光说笑
时光说笑 2020-12-09 10:15

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

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

相关标签:
2条回答
  • 2020-12-09 10:41

    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>
    
    0 讨论(0)
  • 2020-12-09 10:43

    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.

    0 讨论(0)
提交回复
热议问题