私のプロジェクトでは、アプリケーション全体でグローバルに持続する一連のリストを生成できるようにする抽象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;
}
}
}
}
サードパーティのAPIインスタンスごとに1つのキャッシュインスタンスを作成できます。サードパーティのAPIがインスタンスに含まれていない場合は、インスタンス化可能なC#ラッパーにカプセル化します。
キャッシュを大きな1つの大きなルートApplication
オブジェクトのメンバーにすることができます。お持ちでない場合は、ご用意ください。これは、合法的にシングルトンになる可能性がある唯一のものです。 (しかし、それでも、静的メソッドで構成されるという意味ではなく、1回new
edされるという意味で、それはシングルトンでなければなりません。)
シングルトンに対する「深刻な憎悪」に悩まされないでください。 「常に」または「決して」と言う人は無知です。または混乱した。または密かに助けを求めて泣いている。
概念的には、キャッシュはシングルトンです。それを回避する方法はありません。それはどこかの記憶に座って、待っています。多くのリーダーとライターは、1つのメモリチャンクにアクセスしています(ディスクバックアップキャッシュを除く)。依存性注入ソリューションは、抽象化をキャッシュに注入することについて話していると思います。それらの多くは存在する可能性がありますが、十分に深く(実際にはそれほど深くも)掘る場合、キャッシュは1つだけです。
私のチームの経験則では、シングルトンは単一の真実のソースに対しては大丈夫です。それはあなたが扱っているように聞こえます。
あなたの現在のソリューションに関する1つの観察:私の理想的な世界では、カジュアルな消費者として、私が使用しているコードで「キャッシュ」という単語を見たくありません。ユーザーのコレクションが必要な場合は、むしろ
var store = new UserStore();
return store.GetUsers()
より
return SalesForceCache.Users.Data;
「キャッシュ」という言葉は精神的なオーバーヘッドです。私がそれを使用するとき、私は突然、データが「どのように」保存されるのか、私が望む「何が」保存されるのかを考えています。もちろん、UserStore
のフードの下にキャッシュがあるかもしれませんが、それは私には隠されています。そしてそれは良いことです。他のいくつかの配管は、何かがキャッシュされるかどうか、およびどれだけの期間を決定する必要があります。
また、将来の不満はいくつかあります。
使いやすいローカルメモリキャッシュの System.Runtime.Caching.MemoryCache を見てください。機能が豊富で、キー/値キャッシュで一般的なイディオムを使用します。私は絶対にあなたがあなたのアプローチを続けるのを思いとどまらせたくありません!頑張れ! MemoryCacheは興味深い参照として役立つかもしれません。
...次のいずれかとして渡す必要がない場合:a)パラメータまたはb)グローバルに静的。
一つを選ぶ。 3番目のオプションはありません。
「データクエリ」抽象化をパラメーターとして渡し、キャッシュをデコレーターとして使用するか、グローバルに静的なプロパティを介してキャッシュにアクセスします。最初の方法をお勧めします。これにより、依存関係が明示的になり、キャッシュとデータ取得を透過的に分離できるようになるためです。
シングルトンが悪い理由は、SRPに違反しているということです。それらには2つの責任があります。シングルトンが実行することになっているものは何でも、その存続期間を処理します。これら2つは排他的であり、別々にする必要があります。人々がIoCを推奨する理由は、寿命処理をシングルトンの外に移動してIoCコンテナーに移動するためです。