Singleton Pattern¶
Singleton deseni, bir sınıftan yalnızca bir örnek oluşturulmasını garanti eder; ancak yanlış uygulamalar thread-safety sorunlarına ve test edilemez koda yol açabilir.
1. Thread-Safe Olmayan Singleton¶
❌ Yanlış Kullanım: Thread-safety gözetmeden singleton oluşturmak.
public class DatabaseConnection
{
private static DatabaseConnection _instance;
public static DatabaseConnection Instance
{
get
{
if (_instance == null)
{
_instance = new DatabaseConnection(); // Race condition riski
}
return _instance;
}
}
}
✅ İdeal Kullanım: Lazy<T> ile thread-safe singleton oluşturun.
public class DatabaseConnection
{
private static readonly Lazy<DatabaseConnection> _instance =
new Lazy<DatabaseConnection>(() => new DatabaseConnection());
public static DatabaseConnection Instance => _instance.Value;
private DatabaseConnection() { }
}
2. DI Container Yerine Manuel Singleton Yönetimi¶
❌ Yanlış Kullanım: Singleton’ı manuel olarak yönetmek.
public class OrderService
{
private readonly CacheManager _cache = CacheManager.Instance;
public Order GetOrder(int id)
{
return _cache.Get<Order>(id);
}
}
✅ İdeal Kullanım: DI container ile singleton lifecycle yönetimi yapın.
builder.Services.AddSingleton<ICacheManager, CacheManager>();
public class OrderService
{
private readonly ICacheManager _cache;
public OrderService(ICacheManager cache)
{
_cache = cache;
}
public Order GetOrder(int id)
{
return _cache.Get<Order>(id);
}
}
3. Singleton İçinde Scoped Servisleri Kullanmak¶
❌ Yanlış Kullanım: Singleton servis içinde scoped servisi doğrudan inject etmek.
builder.Services.AddSingleton<INotificationService, NotificationService>();
public class NotificationService : INotificationService
{
private readonly AppDbContext _context; // Scoped servis, captive dependency!
public NotificationService(AppDbContext context)
{
_context = context;
}
}
✅ İdeal Kullanım: IServiceScopeFactory ile gerektiğinde scope oluşturun.
public class NotificationService : INotificationService
{
private readonly IServiceScopeFactory _scopeFactory;
public NotificationService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task SendAsync(string message)
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// context güvenli şekilde kullanılabilir
}
}
4. Singleton State ile Test Edilemez Kod¶
❌ Yanlış Kullanım: Statik singleton erişimi ile birim testleri zorlaştırmak.
public class ReportService
{
public Report Generate()
{
var config = AppConfig.Instance; // Statik bağımlılık, mock yapılamaz
return new Report(config.ReportTitle);
}
}
✅ İdeal Kullanım: Interface üzerinden bağımlılık alarak test edilebilir hale getirin.
public class ReportService
{
private readonly IAppConfig _config;
public ReportService(IAppConfig config)
{
_config = config;
}
public Report Generate()
{
return new Report(_config.ReportTitle);
}
}
5. Singleton ile Dispose Edilemeyen Kaynaklar¶
❌ Yanlış Kullanım: Singleton içinde yönetilmeyen kaynakları temizlememek.
public class FileLogger
{
private static readonly FileLogger _instance = new FileLogger();
private readonly StreamWriter _writer;
private FileLogger()
{
_writer = new StreamWriter("app.log", append: true);
}
public static FileLogger Instance => _instance;
public void Log(string message) => _writer.WriteLine(message);
// StreamWriter hiçbir zaman dispose edilmiyor
}
✅ İdeal Kullanım: IDisposable uygulayarak kaynakları düzgün yönetin.
public class FileLogger : IDisposable
{
private readonly StreamWriter _writer;
public FileLogger()
{
_writer = new StreamWriter("app.log", append: true);
}
public void Log(string message) => _writer.WriteLine(message);
public void Dispose()
{
_writer?.Dispose();
}
}
// DI container dispose işlemini otomatik yönetir
builder.Services.AddSingleton<FileLogger>();
6. Double-Check Locking Hatası¶
❌ Yanlış Kullanım: volatile anahtar kelimesi olmadan double-check locking yapmak.
public class ConnectionPool
{
private static ConnectionPool _instance;
private static readonly object _lock = new object();
public static ConnectionPool Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
_instance = new ConnectionPool(); // Instruction reordering riski
}
}
return _instance;
}
}
}
✅ İdeal Kullanım: Lazy<T> kullanarak karmaşık locking’den kaçının.
public class ConnectionPool
{
private static readonly Lazy<ConnectionPool> _instance =
new Lazy<ConnectionPool>(() => new ConnectionPool());
public static ConnectionPool Instance => _instance.Value;
private ConnectionPool() { }
}