Nel mio progetto, ho una classe astratta Cache
che mi consente di popolare una serie di elenchi che persistono globalmente nella mia applicazione. Questi oggetti cache sono sicuri per i thread e possono essere manipolati se necessario, e consentono di ridurre l'enorme sovraccarico di interrogare direttamente le API di terze parti esterne. Ho visto un po 'di odio per i singleton, quindi sono un po' curioso di sapere quali altre opzioni ho quando questo è il mio caso d'uso corrente.
Ho visto un po 'di iniezione di dipendenza, ma non so se è abbastanza adeguato o utile in questo scenario.
Ecco un esempio della mia classe Cache
abstract:
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
}
}
E un esempio della sua implementazione.
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;
}
}
}
}