web-dev-qa-db-ja.com

データのリストをキャッシュするためのシングルトンの代替?

私のプロジェクトでは、アプリケーション全体でグローバルに持続する一連のリストを生成できるようにする抽象Cacheクラスを使用しています。これらのキャッシュオブジェクトはスレッドセーフであり、必要に応じて操作できるため、外部のサードパーティAPIに直接クエリを実行する際の大きなオーバーヘッドを削減できます。シングルトンに対する深刻な嫌悪感を目にしたので、これが現在のユースケースであるときに他にどのようなオプションがあるのか​​少し気になります。

依存性注入がかなり言及されているのを見たことがありますが、それがこのシナリオで十分であるか、または有用であるかどうかはわかりません。

これが私のCache抽象クラスの例です:

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

そしてその実装例。

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. サードパーティのAPIインスタンスごとに1つのキャッシュインスタンスを作成できます。サードパーティのAPIがインスタンスに含まれていない場合は、インスタンス化可能なC#ラッパーにカプセル化します。

  2. キャッシュを大きな1つの大きなルートApplicationオブジェクトのメンバーにすることができます。お持ちでない場合は、ご用意ください。これは、合法的にシングルトンになる可能性がある唯一のものです。 (しかし、それでも、静的メソッドで構成されるという意味ではなく、1回newedされるという意味で、それはシングルトンでなければなりません。)

1
Mike Nakis

シングルトンに対する「深刻な憎悪」に悩まされないでください。 「常に」または「決して」と言う人は無知です。または混乱した。または密かに助けを求めて泣いている。

概念的には、キャッシュはシングルトンです。それを回避する方法はありません。それはどこかの記憶に座って、待っています。多くのリーダーとライターは、1つのメモリチャンクにアクセスしています(ディスクバックアップキャッシュを除く)。依存性注入ソリューションは、抽象化をキャッシュに注入することについて話していると思います。それらの多くは存在する可能性がありますが、十分に深く(実際にはそれほど深くも)掘る場合、キャッシュは1つだけです。

私のチームの経験則では、シングルトンは単一の真実のソースに対しては大丈夫です。それはあなたが扱っているように聞こえます。

あなたの現在のソリューションに関する1つの観察:私の理想的な世界では、カジュアルな消費者として、私が使用しているコードで「キャッシュ」という単語を見たくありません。ユーザーのコレクションが必要な場合は、むしろ

var store = new UserStore();
return store.GetUsers()

より

return SalesForceCache.Users.Data;

「キャッシュ」という言葉は精神的なオーバーヘッドです。私がそれを使用するとき、私は突然、データが「どのように」保存されるのか、私が望む「何が」保存されるのかを考えています。もちろん、UserStoreのフードの下にキャッシュがあるかもしれませんが、それは私には隠されています。そしてそれは良いことです。他のいくつかの配管は、何かがキャッシュされるかどうか、およびどれだけの期間を決定する必要があります。

また、将来の不満はいくつかあります。

  1. 保管メカニズムは厳格です。保存できるのはコレクションのみです。それは私が望むものではないかもしれません。たとえば、アクティブなユーザーを個別に、有効期限をずらして電子メールアドレスで保存したい場合があります。 (多くのユーザーがいて、一度にオンラインになるのはごく一部のみである場合に非常に便利です。)
  2. 有効期限は絶対です。スライドする有効期限を定義すると便利です。 (特にメモリ内で最もアクティブなデータのみが必要な場合のメモリ制約下)
  3. キャッシュの依存関係はありません。キャッシュの依存関係により、特定の条件が満たされたときに、キャッシュされたデータを自動的に期限切れにすることができます。

使いやすいローカルメモリキャッシュの System.Runtime.Caching.MemoryCache を見てください。機能が豊富で、キー/値キャッシュで一般的なイディオムを使用します。私は絶対にあなたがあなたのアプローチを続けるのを思いとどまらせたくありません!頑張れ! MemoryCacheは興味深い参照として役立つかもしれません。

0
Scant Roger

...次のいずれかとして渡す必要がない場合:a)パラメータまたはb)グローバルに静的。

一つを選ぶ。 3番目のオプションはありません。

「データクエリ」抽象化をパラメーターとして渡し、キャッシュをデコレーターとして使用するか、グローバルに静的なプロパティを介してキャッシュにアクセスします。最初の方法をお勧めします。これにより、依存関係が明示的になり、キャッシュとデータ取得を透過的に分離できるようになるためです。

シングルトンが悪い理由は、SRPに違反しているということです。それらには2つの責任があります。シングルトンが実行することになっているものは何でも、その存続期間を処理します。これら2つは排他的であり、別々にする必要があります。人々がIoCを推奨する理由は、寿命処理をシングルトンの外に移動してIoCコンテナーに移動するためです。

0
Euphoric