Chuyển tới nội dung chính

Singleton Pattern (Mẫu Singleton)

Định nghĩa (Definition)

Mẫu Singleton đảm bảo một class chỉ có một instance (thể hiện) và cung cấp điểm truy cập toàn cục (Global Point of Access) đến nó. Điều này hữu ích khi cần chính xác một đối tượng để điều phối các hành động trên toàn hệ thống.

Ví dụ Coffee Shop

Trong một quán cà phê, chỉ nên có một máy pha cà phê (Coffee Machine) kiểm soát quá trình pha — nhiều instance sẽ dẫn đến nhiệt độ không nhất quán và lãng phí tài nguyên.

Triển khai cơ bản (Không Thread-Safe)

public class CoffeeMachine
{
private static CoffeeMachine? _instance;

public static CoffeeMachine Instance
{
get
{
if (_instance is null)
_instance = new CoffeeMachine();
return _instance;
}
}

private CoffeeMachine()
{
Console.WriteLine("☕ Coffee machine initialized — heating up...");
}

private int _waterTemperature = 90;

public Coffee BrewEspresso()
{
Console.WriteLine($" Brewing at {_waterTemperature}°C...");
return new Espresso();
}

public void SetTemperature(int temp) => _waterTemperature = temp;
}

// Usage
var machine = CoffeeMachine.Instance;
var coffee = machine.BrewEspresso();
// Output: ☕ Coffee machine initialized — heating up...
// Brewing at 90°C...
Thread Safety (An toàn luồng)

Phiên bản cơ bản không thread-safe. Hai thread có thể đồng thời thấy _instance là null và tạo ra hai instance.

Thread-Safe với lock

public class CoffeeMachine
{
private static CoffeeMachine? _instance;
private static readonly object _lock = new();

public static CoffeeMachine Instance
{
get
{
lock (_lock)
{
if (_instance is null)
_instance = new CoffeeMachine();
return _instance;
}
}
}

private CoffeeMachine() { }
}

Thread-Safe với Lazy<T> (Khuyến nghị)

public class CoffeeMachine
{
private static readonly Lazy<CoffeeMachine> _instance =
new(() => new CoffeeMachine());

public static CoffeeMachine Instance => _instance.Value;

private CoffeeMachine()
{
Console.WriteLine("☕ Coffee machine initialized");
}

private int _orderNumber = 0;

public string GenerateOrderId()
{
Interlocked.Increment(ref _orderNumber);
return $"ORD-{_orderNumber:D4}";
}
}

// Usage — thread-safe, lazily initialized (khởi tạo lười biếng)
var order1 = CoffeeMachine.Instance.GenerateOrderId(); // ORD-0001
var order2 = CoffeeMachine.Instance.GenerateOrderId(); // ORD-0002

.NET: DI-Managed Singleton (Singleton quản lý bởi DI)

Trong ASP.NET Core, ưu tiên DI-managed singleton thay vì mẫu truyền thống. DI container xử lý thread safety và làm cho các phụ thuộc (Dependencies) trở nên rõ ràng.

// Service registration (Đăng ký service)
builder.Services.AddSingleton<CoffeeMachine>();

// Constructor injection — phụ thuộc rõ ràng và dễ kiểm thử
public class OrderService
{
private readonly CoffeeMachine _coffeeMachine;

public OrderService(CoffeeMachine coffeeMachine)
{
_coffeeMachine = coffeeMachine;
}

public string CreateOrder()
{
var orderId = _coffeeMachine.GenerateOrderId();
return orderId;
}
}

Tại sao DI Singleton tốt hơn?

Singleton truyền thốngDI Singleton
Phụ thuộc ẩn (Hidden Dependency) — gọi CoffeeMachine.Instance ở khắp nơiPhụ thuộc rõ ràng (Explicit Dependency) — constructor injection
Khó unit test (không thể thay thế instance)Dễ mock hoặc thay thế trong test
Trạng thái toàn cục có thể thay đổi (Global Mutable State)Phạm vi giới hạn trong DI container
Tự quản lý thread safetyContainer quản lý thread safety

Khi nào sử dụng (When to Use)

  • Cấu hình (Configuration) hoặc cài đặt dùng chung
  • Connection pool hoặc quản lý tài nguyên (Resource Manager)
  • Logging service
  • Lớp cache (Caching Layer)

Khi nào KHÔNG sử dụng (When NOT to Use)

  • Dùng như "biến toàn cục" (Global Variable) để truyền dữ liệu giữa các class — hãy dùng DI đúng cách
  • Khi cần nhiều instance trong các scope khác nhau — dùng lifetime Scoped trong DI
  • Khi instance chứa trạng thái có thể thay đổi (Mutable State) mà test cần reset — dùng DI và tạo mới cho mỗi test

Điểm chính (Key Takeaways)

  • Dùng Lazy<T> cho khởi tạo lười biếng thread-safe (Thread-Safe Lazy Initialization) nếu phải triển khai mẫu thủ công
  • Trong ASP.NET Core, luôn ưu tiên services.AddSingleton<T>() thay vì mẫu truyền thống
  • Singleton giới hạn ở một instance — các lifetime ScopedTransient của DI phục vụ nhu cầu khác
  • Private constructor ngăn chặn khởi tạo từ bên ngoài (External Instantiation)

Câu hỏi phỏng vấn (Interview Questions)

Q: Singleton có thread-safe mặc định không? Không. Bạn cần lock, Lazy<T>, hoặc static initializer. Lazy<T> là cách tiếp cận sạch nhất.

Q: Singleton khác gì với static class? Static class không thể implement interface, không thể truyền làm tham số, và không thể lazy-initialize. Singleton là một đối tượng thực sự với một instance duy nhất.

Q: Tại sao Singleton được coi là anti-pattern? Nó引入 trạng thái toàn cục ẩn (Hidden Global State) và tight coupling (liên kết chặt), làm cho việc kiểm thử khó khăn. Trong .NET hiện đại, DI-managed singleton giải quyết cùng vấn đề một cách minh bạch.

  • Dependency Injection — Singleton được quản lý bởi DI
  • Factory Method — một cách khác để kiểm soát việc tạo đối tượng
  • SOLID — DIP — phụ thuộc vào abstraction, không phụ thuộc vào concrete singleton