Dans mon projet, j'ai un résumé Cache
classe qui me permet de remplir une série de listes qui persistent globalement tout au long de mon application. Ces objets de cache sont du thread-coffre-fort et peuvent être manipulés au besoin, et permettez-moi de réduire les frais généraux d'interroger directement les API de tiers externes. J'ai vu une haine sérieuse pour les singletons, donc je suis un peu curieux quelles autres options que j'ai quand c'est mon cas d'utilisation actuel.
J'ai vu une injection de dépendance mentionnée un peu, mais je ne sais pas si cela est assez adéquat ou utile dans ce scénario.
Voici un exemple de mon Cache
abstrait classe:
public abstract class Cache<TU, T>
where TU : Cache<TU, T>, new()
where T : class
{
private static readonly TU Instance = new TU();
private static volatile State _currentState = State.Empty;
private static volatile object _stateLock = new object();
private static volatile object _dataLock = new object();
private static DateTime _refreshedOn = DateTime.MinValue;
private static T InMemoryData { get; set; }
public static T Data
{
get
{
switch (_currentState)
{
case State.OnLine:
var timeSpentInCache = (DateTime.UtcNow - _refreshedOn);
if (timeSpentInCache > Instance.GetLifetime())
{
lock (_stateLock)
{
if (_currentState == State.OnLine) _currentState = State.Expired;
}
}
break;
case State.Empty:
lock (_dataLock)
{
lock (_stateLock)
{
if (_currentState == State.Empty)
{
InMemoryData = Instance.GetData();
_refreshedOn = DateTime.UtcNow;
_currentState = State.OnLine;
}
}
}
break;
case State.Expired:
lock (_stateLock)
{
if (_currentState == State.Expired)
{
_currentState = State.Refreshing;
Task.Factory.StartNew(Refresh);
}
}
break;
}
lock (_dataLock)
{
if (InMemoryData != null) return InMemoryData;
}
return Data;
}
}
public static T PopulateData()
{
return Data;
}
protected abstract T GetData();
protected virtual TimeSpan GetLifetime()
{
return TimeSpan.FromMinutes(10);
}
private static void Refresh()
{
if (_currentState != State.Refreshing) return;
var dt = Instance.GetData();
lock (_stateLock)
{
lock (_dataLock)
{
_refreshedOn = DateTime.UtcNow;
_currentState = State.OnLine;
InMemoryData = dt;
}
}
}
public static void Invalidate()
{
lock (_stateLock)
{
_refreshedOn = DateTime.MinValue;
_currentState = State.Expired;
}
}
private enum State
{
Empty,
OnLine,
Expired,
Refreshing
}
}
Et un exemple de sa mise en œuvre.
public class SalesForceCache
{
public class Users : Cache<Users, List<Contact>>
{
protected override List<Contact> GetData()
{
var sf = new SalesForce();
var users = sf.GetAllUsers();
sf.Dispose();
return users;
}
protected override TimeSpan GetLifetime()
{
try
{
return TimeSpan.FromDays(1);
}
catch (StackOverflowException)
{
return TimeSpan.Zero;
}
}
}
public class Accounts : Cache<Accounts, List<Account>>
{
protected override List<Account> GetData()
{
var sf = new SalesForce();
var accounts = sf.GetAllAccounts();
sf.Dispose();
return accounts;
}
protected override TimeSpan GetLifetime()
{
try
{
return TimeSpan.FromDays(1);
}
catch (StackOverflowException)
{
return TimeSpan.Zero;
}
}
}
}
Vous pouvez créer une instance de cache par instance d'API tiers. Si vos API tiers ne viennent pas dans des cas, puis encapsulez-les dans un emballage C # instanciable.
Vous pouvez faire vos membres de vos caches de votre une excellente grande racine Application
objet. Si vous n'en avez pas, venez avec un. C'est la seule chose qui pourrait légitimement être un singleton. (Mais même alors, il ne devrait s'agir que d'un singleton dans le sens où il sera new
ed une fois, pas dans le sens de la composition de méthodes statiques.)
Ne laissez pas la "haine grave" pour les singletons vous déranger. Toute personne qui dit "toujours" ou "jamais" est ignorante. Ou dérangé. Ou pleurer secrètement pour l'aide.
Conceptuellement, un cache est un singleton. Il n'y a pas de se déplacer. Il est assis en mémoire quelque part, attendant. De nombreux lecteurs et écrivains accèdent à un seul morceau de mémoire (sauf un cache soutenu de disque). J'imagine que les solutions d'injection de dépendance parlent d'injecter une abstraction sur le cache. Il peut y en avoir beaucoup d'entre eux, mais si vous creusez assez profondément (pas même si profonde, vraiment), il ne devrait jamais être un cache.
Le règne de ma équipe est que Singletons peut sera correct pour une seule source de vérité. Cela ressemble à ce que vous traitez.
Une observation sur votre solution actuelle: dans mon monde idéal, comme un consommateur occasionnel, je ne veux jamais voir le mot "cache" dans le code que j'utilise. Si je veux une collection d'utilisateurs, je préférerais dire
var store = new UserStore();
return store.GetUsers()
que
return SalesForceCache.Users.Data;
Le mot "cache" est un surcharge mental. Lorsque je l'utilise, je pense soudainement à la "Comment" les données sont stockées par rapport à "Quoi" je veux. Bien sûr, il peut y avoir une cache sous le capot de mon UserStore
, mais c'est caché de moi. Et c'est une bonne chose. Une autre plomberie devrait décider si quelque chose est mis en cache et pendant combien de temps.
En outre, il y a quelques frustrations futures possibles:
Jetez un coup d'oeil à - system.runtime.caching.memorycache pour un cache de mémoire local facile à utiliser. Il est riche et utilise les idiomes communes dans les caches de clé/valeur. Je ne veux absolument pas vous décourager de continuer votre approche! Persévère! Le matiche MemoryCache pourrait servir de référence intéressante.
... Sans que cela doive être transmis comme suit: a) un paramètre ou b) globalement statique.
Choisir un. Il n'y a pas de troisième option.
Vous transmettez l'abstraction "Data Intersectionning" en tant que paramètre, au cache servant de décorateur ou accédez au cache via une propriété statique globale. Je recommanderais le premier, car alors cela rend la dépendance explicite et vous permet de séparer de manière transparente l'acquisition de la mise en cache et des données.
Ma prise pour laquelle les singletons sont mauvais, c'est qu'ils brisent SRP. Ils ont deux responsabilités: tout ce que le singleton est censé faire et remettre sa vie. Ces deux sont exclusifs et doivent être séparés. Raison pour laquelle les gens recommandent au CIO consiste à déplacer la manipulation de la vie en dehors du singleton et dans le conteneur IOC.