web-dev-qa-db-fra.com

Alternatives à Singletons pour la mise en cache des listes de données?

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;
            }
        }
    }
}
6
JD Davis
  1. 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.

  2. 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 newed une fois, pas dans le sens de la composition de méthodes statiques.)

1
Mike Nakis

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:

  1. Le mécanisme de stockage est rigide. Vous ne pouvez jamais stocker une collection seulement. Ce n'est peut-être pas ce que je veux. Par exemple, peut-être que je souhaite stocker des utilisateurs actifs individuellement, avec une expiration glissante, par adresse électronique. (Super utile lorsque vous avez beaucoup d'utilisateurs, mais seulement une poignée d'entre eux sont en ligne à la fois.)
  2. Les expirations sont absolues. Il peut être utile de définir une expiration glissante. (surtout sous contrainte de mémoire lorsque vous ne voulez que les données les plus actives en mémoire)
  3. Il n'y a pas de dépendances de cache. Les dépendances de cache vous permettent d'expirer automatiquement des données en cache lorsque des conditions spécifiques sont remplies.

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.

0
Scant Roger

... 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.

0
Euphoric