2011-11-15 17 views
17

Tengo 2 clases genéricas, una clase BaseComponent y una clase BaseManager.parámetros de tipo genérico circular

Ambos son abstractos y están destinados a ser hechos de hormigón.

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

BaseManager tiene una lista de BaseComponents, por lo que quiero que sea genérico, por lo que un PhysicsManager : BaseManager<PhysicsComponent> tendría una lista de PhysicsComponents.

Quiero (o más bien, creo que necesito) BaseComponent ser genérico porque solo quiero que las clases derivadas de BaseComponent sean 'adjuntas' a su administrador apropiado. Idealmente, no quiero tener que escribir un constructor por componente derivado solo para poder agregarlo a una clase de administrador concreta aprobada. Idealmente quiero tener un constructor que tome la clase abstracta BaseManager.

¿Cómo puedo gestionar este tipo de dependencia circular?

+0

Consideraría seriamente rediseñar para evitar la dependencia circular. Por ejemplo, make 'BaseComponent' no genérico. Haz que dependa de un 'IManager'. Ponga el elenco de 'BaseComponent' a' TComponent' en 'BaseManager ' –

+0

Estoy de acuerdo que es un poco apestoso como Jon señala, pero no lo entiendo del todo. Si 'BaseComponent' dependía de un' IManager', ¿cómo me aseguraría de que todas las clases derivadas de 'BaseComponent' tuvieran un constructor que aceptara la implementación correcta de' IManager' para poder agregarlo a la lista del Manager? Si tienes tiempo, apreciaría una explicación en una respuesta. –

Respuesta

25

suena como es posible que desee tener dos parámetros de tipo genérico:

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> 

Sí, huele mal - pero ese es el tipo de cosas que he hecho en Protocol Buffers.

Así entonces tendríamos:

public class PhysicsManager : BaseManager<PhysicsComponent, PhysicsManager> 

public class PhysicsComponent : BaseComponent<PhysicsComponent, PhysicsManager> 
2

El acoplamiento flojo sería si los componentes no sabían de sus directivos. Aquí hay un ejemplo de cómo eso funcionaría. Tenga en cuenta que este enfoque requiere algún tipo de mecanismo de fábrica si todos los componentes deben agregarse a un administrador. (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); 
} 

Si los componentes no pueden ser independientes de sus gerentes, creo que sería mejor que dependen de una interfaz definida por la necesidad del componente. Esta es la 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(); 
    } 
} 

La desventaja es que el sistema de tipos no hace cumplir que el gerente correcta se pasa, y el elenco de BaseManager entonces fallar. Todavía preferiría esta manera y "mantendría la olfato en mi infraestructura" en lugar de tener plantillas circulares que contaminen a todos mis componentes y gerentes de concreto.

+0

¡Muchas gracias por la respuesta detallada! –

+0

Interesante. Tenga en cuenta que las dependencias circulares se pueden hacer utilizando interfaces puras, para que no contaminen los componentes y administradores concretos. Los nombres de tipos genéricos se pueden acortar con las directivas 'using' (por ejemplo, http://stackoverflow.com/a/161484/1429390). –

Cuestiones relacionadas