2009-09-10 9 views
5

Estoy tratando de implementar un identity map usando genéricos. Tengo una clase abstracta, Entidad y una restricción de derivación en mi mapa para Entidad. Como mi mapa necesita poder crear instancias de entidades, mi mapa también tiene una restricción de constructor.Mapa genérico de identidad en C#. No quiero el constructor público

Sin embargo, para que el mapa sea útil, las subclases de Entidad no deberían poder crearse una instancia del código del cliente, lo que significa que me gustaría tener un constructor interno y ningún constructor público. Sin embargo, esto entra en conflicto con la restricción del constructor.

¿Hay algo que me falta? ¿Hay alguna forma de refactorizar esto para obtener el resultado deseado?

El código siguiente se compila como está, pero, idealmente, los constructores de subclases entidad estaría interna:

public abstract class Entity 
{ 
    public int Id { get; protected internal set; } 
} 

public sealed class Widget : Entity 
{ 
    // Client code should not be allowed to instantiate entities. 
    // But the constraints on EntityMap require that entities have 
    // a public constructor. 
    public Widget() { } 
} 

public sealed class Gadget : Entity 
{ 
    public Gadget() { } 
} 

// The new() constraint is required so that Get() can instantiate Ts. 
public class EntityMap<T> where T : Entity, new() 
{ 
    private Dictionary<int, T> _entities = new Dictionary<int, T>(); 
    private object _getLock = new object(); 

    public T Get(int id) 
    { 
     lock (_getLock) 
     { 
      if (!_entities.ContainsKey(id)) 
       _entities.Add(id, new T() { Id = id }); 
     } 

     return _entities[id]; 
    } 

    // Client code should not be allowed to instantiate maps. 
    internal EntityMap() { } 
} 

// Ideally, the client would only be able to obtain Entity 
// references through EntityMaps, which are only accessible 
// through the ApplicationMap. 
public static class ApplicationMap 
{ 
    public static EntityMap<Widget> Widgets = new EntityMap<Widget>(); 
    public static EntityMap<Gadget> Gadgets = new EntityMap<Gadget>(); 
} 

Respuesta

9

En lugar de requerir una restricción constructor, un pase de Func<T> al mapa constructor. De esta forma el constructor puede ser interna, pero el mapa todavía puede llamar con eficacia:

public class EntityMap<T> where T : Entity 
{ 
    private readonly Dictionary<int, T> _entities = new Dictionary<int, T>(); 
    private readonly object _getLock = new object(); 
    private readonly Func<T> _entityGenerator; 

    public T Get(int id) 
    { 
     lock (_getLock) 
     { 
      T ret; 
      if (!_entities.TryGetValue(id, ret)) 
      { 
       ret = entityGenerator(); 
       newEntity[id] = ret; 
       ret.Id = id; 
      } 

      return ret; 
     } 
    } 

    internal EntityMap(Func<T> entityGenerator) 
    { 
     _entityGenerator = entityGenerator; 
    } 
} 

Entonces inicializarlo con:

EntityMap<Widget> widgetMap = new EntityMap(() => new Widget()); 

Usted podría hacerlo un Func<int, T> lugar y hacer que el delegado responsable de creando una entidad con la ID correcta. De esta forma, puede hacer que su ID sea de solo lectura, tomándolo como parámetro para el constructor Entity.

(. Me he tomado la libertad de hacer su método Get más eficiente, por cierto)

+0

¿Cuál es el beneficio de usar TryGetValue en lugar de ¿ContainsKey? ¿Es un problema de velocidad? – Lobstrosity

2

Gracias a Jon, aquí está el código de trabajo:

public abstract class Entity 
{ 
    private readonly int _id; 

    public int Id 
    { 
     get { return _id; } 
    } 

    internal Entity(int id) 
    { 
     _id = id; 
    } 
} 

public sealed class Widget : Entity 
{ 
    internal Widget(int id) : base(id) { } 
} 

public sealed class Gadget : Entity 
{ 
    internal Gadget(int id) : base(id) { } 
} 

public class EntityMap<T> where T : Entity 
{ 
    private readonly Dictionary<int, T> _entities = new Dictionary<int, T>(); 
    private readonly object _getLock = new object(); 
    private readonly Func<int, T> _entityGenerator; 

    public T Get(int id) 
    { 
     lock (_getLock) 
     { 
      T entity; 

      if (!_entities.TryGetValue(id, out entity)) 
       _entities[id] = entity = _entityGenerator(id); 

      return entity; 
     } 
    } 

    internal EntityMap(Func<int, T> entityGenerator) 
    { 
     _entityGenerator = entityGenerator; 
    } 
} 

public static class ApplicationMap 
{ 
    public static readonly EntityMap<Widget> Widgets = new EntityMap<Widget>(id => new Widget(id)); 
    public static readonly EntityMap<Gadget> Gadgets = new EntityMap<Gadget>(id => new Gadget(id)); 
} 
+1

Será mejor que publique esto como una edición de su publicación original en lugar de como una respuesta alternativa. – jpierson

Cuestiones relacionadas