
1 – Gizli Bağımlılık Problemi
Singletone bir nesneyi DI kullanarak ihtiyaç duyulan nesneye vermek gerekir. Aksi takdirde aşağıdaki gibi hatalı bir davranış test yazarken singleton nesneyi mock edememene sebep olur. Sınıfın dışa bağımlılığı yokmuş gibi görünmesine sebep olur.
public class KitchenService
{
public void PreparingOrder()
{
var logger = LoggerSingleton.Instance;
logger.log("Order Preapering");
}
}Bu sınıfı test ederken LoggerSingleton’ı mock edemezsiniz çünkü KitchenService sınıfı içinde bir constructor bulunmuyor.
Bunun yerine DI kullanarak Singletone nesneyi dışarıdan verebilirsiniz:
public class KitchenService
{
private readonly ILogger _logger;
public KitchenService(ILogger logger)
{
_logger = logger;
}
public void PreparingOrder()
{
_logger.Log("Order Preapering.");
}
}
2 – Global Durum Oluşması. Sonuç => Sıkı Bağlılık
Bunu en aza indirmek için kullanabileceğimiz bazı yöntemler var ki zaten DI kullanan yazılımcı bunu hemen farkedecektir.
- Dependency Injection Kullan
Kullanılmamış hali şöyle:
public class LocationGenerator
{
public void Generate()
{
Logger.Instance.Log("Generating location..");
}
}DI ile eklenmemiş, test edilemez.
Olması gereken:
public class LocationGenerator
{
private readonly ILogger _logger;
public LocationGenerator(ILogger logger)
{
_logger = logger;
}
public void Generate()
{
_logger.Log("Generating location..");
}
}Logger hala bir singletone ama bağlılık sıkı değil, olması gerektiği gibi gevşek.
- Singleton’u uygulama yönetsin
Sınıflar Instance’e ulaşamaz, sınıflar interface kullanarak ihtiyacını görür.
builder.Services.AddSingleton<ILogger, Logger>();- Singleton üzerindeki herhangi bir property’e değer dışarıdan atanabilir olmamalı.
Çünkü Stateful Singleton tehlikelidir. State zaten değişebilen veri anlamına gelir yani durum. Durum değişkendir. Nesne singleton olduğu için uygulama içinde tek bir yerden erişilir ve eğer set edilebilen bir property kulanmışsanız herkes onu değiştirebilir.
Mesela örnek:
public class ExampleManager
{
private static readonly ExampleManager _instance = new();
public static ExampleManager Instance = _instance;
public int CurrentExampleId { get; set; } //tehlike burada
}Bu durumda biri gider şunu yapabilir :
ExampleManager.Instance.CurrentExampleId = 666;
Daha sonra belki başka bir yerde bu kullanılır:
var currentId = ExampleManager.Instance.CurrentExampleId;Ya başka bir thread şunu yaparsa? :
ExampleManager.Instance.CurrentExampleId = 1;Belirsiz ve hatalara açık. Birden fazla kullanıcı için çakışma.
Peki nasıl olmalı?
Basit, set edilebilir bir değer olmamalı. Ya readonly ya sabit yani const ya da sadece okunabilir property ler olmalı. Bununla ilgili bir sürü örnek vardır zaten. Örneğin Options Pattern kullanarak applicationsettings dosyasından verileri okuyan ve sunan bir sınıf. Bu sınıfın tek amacı belirlenmiş değerleri uygulama katmanına sunmak ve böyle bir sınıfın zaten Stateful olmasının bir anlamı yok.
3 – Thread Güvenliği Sorunları
Multi-Threaded uygulamalarda Singleton aynı zamanda birden fazla instance oluşturabilir (yanlış kullanımda).
Olmaması gereken :
public class ExampleSingleton
{
private static ExampleSingleton _instance;
public static ExampleSingleton Instance => _instance ?? (_instance = new ExampleSingleton());
}Burada iki thread aynı anda instance oluşturabilir.
Olması gereken:
public class ExampleSingleton
{
private static readonly Lazy<ExampleSingleton> _instance = new(() => new ExampleSingleton());
public static ExampleSingleton Instance => _instance.value;
private ExampleSingleton() {}
}Lazy bir nesneyi oluşturduktan sonra bir daha oluşturmaz. Dolayısıyla multi-threaded durumlarında gayet güvenlidir.
Önerebileceğim bazı makale ve kitaplar:
https://learn.microsoft.com/en-us/dotnet/api/system.lazy-1?view=net-9.0
https://refactoring.guru/design-patterns/singleton
https://www.oreilly.com/library/view/dependency-injection-in/9781935182504
Bir yanıt yazın