最近在整理一些老專案的時候,我發現自己以前為了快速方便,常常會直接在程式裡手動寫 Singleton

傳統我們的程式碼大概會長得像是這樣
public class Logger
{
private static readonly Logger _instance = new Logger();
private Logger() {
Console.WriteLine("Looger Ctor");
}
public static Logger Instance => _instance;
public void Log(string message)
{
Console.WriteLine($"[{DateTime.Now}] {message}");
}
}
// 使用方法
// Parallel.For(0, 100, i =>
// {
// Logger.Instance.Log("LOG "+i);
// });
在 .netcore 之後,基本上都會看到很多文件都建議使用官方的 AddSingleton 其優點為
GPT:
以前的 Singleton 是:
物件自己控制自己。
測試時你沒辦法 mock。
很多地方直接 Logger.Instance.Log(),耦合死死的。
而現在:
所有依賴都由 DI 管理。
單元測試時可以輕鬆換掉 Logger。
架構清晰,生命週期由框架控制。
基本上,因為現在很多都是使用使用 .Net 推薦新的作法,我們能夠按照微軟推的標準就盡量使用
如果你會遇見引入錯誤 記得引入 Microsoft.Extensions.DependencyInjection , Microsoft.Extensions.Hosting
接下來就是改造後的程式碼
public interface ILoggerService
{
void Log(string message);
}
public class ConsoleLogger : ILoggerService
{
public ConsoleLogger() {
Console.WriteLine("ConsoleLogger Ctor");
}
public void Log(string message)
{
Console.WriteLine($"[{DateTime.Now}] {message}");
}
}
使用方法
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
static void Main(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
// 註冊服務
builder.Services.AddSingleton();
var app = builder.Build();
// 從 DI 容器取用服務
var logger = app.Services.GetRequiredService();
Parallel.For(0, 100, i =>
{
Logger.Instance.Log("AddSingleton-LOG " + i);
});
}
從手寫 Singleton 換成 AddSingleton(),
一開始可能會覺得多此一舉,但當你開始需要測試、擴充、分層設計時,這種差異會越來越明顯
以前我們是自己管理"只有一個",現在則是讓框架幫我們"確保只有一個"
結果一樣,但代價完全不同,傳統 Singleton 是程式自己管自己,而 AddSingleton 是讓系統幫你管程式