Я бы не советовал использовать класс, который определяет свой собственный синглтон, просто потому, что это обычно означает, что у вас будут проблемы во время модульного тестирования.
Если вы используете универсальный синглтон, у вас будет та же функциональность, но с дополнительными преимуществами позже при тестировании / отходе от шаблона синглтона (например, для нескольких пользователей).
Обратите внимание, что синглтон инициализируется делегатом. Обоснование здесь заключается в том, что делегат будет вызываться только один раз, даже если два потока каким-то образом одновременно регистрируют синглтон ...
Использование интерфейса облегчает вашу жизнь при написании юнит-тестов, поскольку вы можете смоделировать интересующую вас часть синглтона (или вашу сверхбыструю - за 2 минуты до перехода на сеанс патча / отладки CEO).
Это может быть излишним для хранения кортежа входа в систему / прохода, но этот шаблон спас мой бекон больше раз, чем я хочу сосчитать.
public static class Singleton<T>
{
private static T instance;
private static readonly object sync = new object();
static bool registered = false;
public static T Instance
{
get
{
return instance;
}
}
public static void Register(Func<T> constructor)
{
lock (sync)
{
if (!registered)
{
instance = constructor();
registered = true;
}
}
}
}
class Demo
{
class Data
{
public string Pass { get; set; }
public string Login { get; set; }
}
void SimpleUsage()
{
string login = "SEKRIT";
string pass = "PASSWORD";
// setup
Singleton<Data>.Register(() => new Data { Login = login, Pass = pass });
//
var ltCommander = Singleton<Data>.Instance;
}
/// <summary>
/// Using an interface will make the singleton mockable for tests!
/// That's invaluable when you'll want to fix something FAST without running the whole app!
/// </summary>
interface IData
{
string Login { get; }
string Password { get; }
}
class UnitTestFriendlyData : IData
{
public UnitTestFriendlyData(string login, string password)
{
Login = login;
Password = password;
}
public string Login { get; private set; }
public string Password { get; private set; }
}
void SmarterUsage()
{
// same setup, but through the interface.
Singleton<IData>.Register(() => new UnitTestFriendlyData("login", "pass"));
// and same for the retrieval
var data = Singleton<IData>.Instance;
}
void UnitTestSetupWithMoq()
{
// Register a mock.
var mock = new Mock<IData>();
mock.SetupProperty(x => x.Login, "Login");
mock.SetupProperty(x => x.Pass, "Pass");
Singleton<IData>.Register(() => mock.Object);
// and same for the retrieval
var data = Singleton<IData>.Instance;
}
}