2009-09-10 4 views
5

Sto cercando di implementare un identity map utilizzando i generici. Ho una classe astratta, Entità e un vincolo di derivazione sulla mia mappa per Entità. Poiché la mia mappa deve essere in grado di creare un'istanza delle entità, la mia mappa ha anche un vincolo di costruttore.Mappa di identità generica in C#. Non voglio un costruttore pubblico

Tuttavia, affinché la mappa sia utile, le sottoclassi di Entità non dovrebbero poter essere istanziate dal codice client, il che significa che vorrei un costruttore interno e nessun costruttore pubblico. Ciò tuttavia è in conflitto con il vincolo del costruttore.

C'è qualcosa che mi manca? C'è un modo per refactoring questo per ottenere il risultato desiderato?

Il seguente codice viene compilato così com'è, ma, idealmente, costruttori sottoclassi di entità sarebbe 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>(); 
} 

risposta

9

Invece di richiedere un vincolo costruttore, passare un Func<T> alla mappa costruttore. In questo modo il costruttore può essere interno, ma la mappa può ancora efficacemente chiamare:

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; 
    } 
} 

Poi inizializzare con:

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

Si potrebbe renderlo un Func<int, T> invece e rendere il delegato responsabile creando un'entità con l'ID giusto. In questo modo è possibile rendere il proprio ID in sola lettura, assumendolo come parametro per il costruttore Entity.

(. Mi sono preso la libertà di fare il metodo di Get più efficiente, btw)

+0

Qual è il vantaggio di utilizzare TryGetValue invece di ContainsKey? È un problema di velocità? – Lobstrosity

2

grazie a Jon, ecco il codice di lavoro:

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

Faresti meglio a postare questo come una modifica al tuo post originale anziché come risposta alternativa. – jpierson

Problemi correlati