WPF(Windows Presentation Foundation) 애플리케이션 개발 시, 특정 데이터나 상태를 애플리케이션 전역에서 공유해야 할 때가 많습니다. 이때 가장 널리 사용되는 디자인 패턴이 싱글톤(Singleton) 패턴입니다. 싱글톤 패턴은 클래스의 인스턴스를 단 하나만 생성하도록 보장하여 메모리 낭비를 줄이고 데이터의 일관성을 유지하는 데 도움을 줍니다.
1. 기본적인 싱글톤과 속성 변경 알림 구현
WPF의 데이터 바인딩과 연동하기 위해서는 싱글톤 클래스에 INotifyPropertyChanged 인터페이스를 구현하는 것이 좋습니다. 이를 통해 싱글톤 인스턴스 내부의 값이 변경될 때 UI가 자동으로 업데이트됩니다.
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class AppStateManager : INotifyPropertyChanged
{
private static AppStateManager _instance;
private string _statusMessage;
// 외부에서 인스턴스화를 방지하기 위한 private 생성자
private AppStateManager() { }
// 전역 접근 지점
public static AppStateManager Instance => _instance ??= new AppStateManager();
public string StatusMessage
{
get => _statusMessage;
set
{
if (_statusMessage != value)
{
_statusMessage = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2. 정적 초기화를 이용한 단순한 싱글톤
가장 단순한 형태의 싱글톤은 정적 필드 초기화 기능을 사용하는 것입니다. .NET의 정적 생성자는 스레드 안전(Thread-safe)을 보장하므로 복잡한 로직 없이도 안전하게 인스턴스를 생성할 수 있습니다.
public sealed class LoggerService
{
// 클래스 로드 시 인스턴스 생성 (Eager Initialization)
private static readonly LoggerService _instance = new LoggerService();
private LoggerService()
{
ServiceName = "Standard Logger";
}
public static LoggerService Instance => _instance;
public string ServiceName { get; }
public void WriteLog(string message)
{
System.Diagnostics.Debug.WriteLine($"[{DateTime.Now}] {message}");
}
}
3. 스레드 안전을 보장하는 이중 확인 잠금(Double-Checked Locking)
멀티스레드 환경에서 인스턴스가 필요한 시점에만 생성(Lazy Initialization)되도록 하려면 lock 키워드를 활용한 이중 확인 잠금 방식을 사용할 수 있습니다.
public sealed class DatabaseConnector
{
private static DatabaseConnector _instance = null;
private static readonly object _lockHandle = new object();
private DatabaseConnector()
{
ConnectionID = Guid.NewGuid().ToString();
}
public static DatabaseConnector Instance
{
get
{
// 첫 번째 체크: 락을 걸기 전 성능 최적화
if (_instance == null)
{
lock (_lockHandle)
{
// 두 번째 체크: 인스턴스 생성 여부 재확인
if (_instance == null)
{
_instance = new DatabaseConnector();
}
}
}
return _instance;
}
}
public string ConnectionID { get; private set; }
public void Connect()
{
// 연결 로직 구현
}
}
위의 예제들처럼 싱글톤 패턴을 적용하면 WPF 애플리케이션 내의 다양한 ViewModel이나 View에서 동일한 데이터 소스에 접근할 수 있습니다. AppStateManager.Instance.StatusMessage = "Ready";와 같은 방식으로 전역 상태를 손쉽게 관리할 수 있습니다.