public class Logger
{
// 로그 메시지를 저장할 StringBuilder입니다.
// StringBuilder는 문자열을 효율적으로 누적할 수 있는 클래스입니다.
private readonly StringBuilder _logs = new StringBuilder();
// 로그 기록 메서드입니다. 현재 시간과 함께 메시지를 문자열로 저장합니다.
// "DateTime.Now"는 현재 시간을 가져오며, "AppendLine"은 줄바꿈 포함 추가입니다.
public void Log(string message)
{
_logs.AppendLine($"{DateTime.Now}: {message}");
}
// 지금까지 기록된 로그들을 하나의 문자열로 반환합니다.
// "ToString()"은 StringBuilder에 저장된 전체 문자열을 반환합니다.
public string GetLogs()
{
return _logs.ToString();
}
}
✔️ Logger 클래스 >> Singleton 패턴으로 만들기
// Logger 클래스는 애플리케이션 전역에서 하나만 존재해야 하는 로깅 클래스입니다.
// Singleton 패턴을 적용하여 인스턴스가 하나만 생성되고 어디서든 접근 가능하도록 만듭니다.
public sealed class Logger
{
// [1] 정적(static) 필드: 클래스의 인스턴스를 저장하는 필드입니다.
// "static" 키워드는 클래스 단위로 존재하며 프로그램 실행 동안 하나만 유지됩니다.
// "Logger?"는 널 가능 타입으로, 아직 인스턴스가 생성되지 않았음을 나타냅니다.
private static Logger? _instance = null;
// [2] 동기화용 lock 객체입니다.
// 여러 스레드가 동시에 Instance 프로퍼티에 접근할 경우,
// 한 번에 하나의 스레드만 인스턴스를 생성하도록 보장합니다.
// "readonly"는 생성자 또는 선언 시 한 번만 할당할 수 있게 합니다.
private static readonly object _lock = new();
// [3] 로그 메시지를 저장할 StringBuilder입니다.
// StringBuilder는 문자열을 효율적으로 누적할 수 있는 클래스입니다.
private readonly StringBuilder _logs = new StringBuilder();
// [4] 생성자: 외부에서 new Logger()를 호출하지 못하도록 private으로 지정합니다.
// Singleton 패턴의 핵심으로, 외부 생성 차단을 통해 오직 Instance를 통해서만 생성됩니다.
private Logger()
{
Console.WriteLine("Logger 인스턴스가 생성되었습니다.");
}
// [5] 외부에서 Logger 인스턴스를 얻을 수 있는 유일한 접근 지점입니다.
// get 액세서에서 인스턴스를 생성하거나 이미 있는 것을 반환합니다.
public static Logger Instance
{
get
{
// [6] lock 블록: 멀티스레드 환경에서 스레드 간 충돌을 방지합니다.
// "lock" 키워드는 특정 코드 영역을 한 번에 하나의 스레드만 실행하도록 제한합니다.
lock (_lock)
{
// [7] Lazy Initialization: 인스턴스가 아직 생성되지 않았다면 생성합니다.
// "??="는 널 병합 할당 연산자로, 왼쪽이 null일 경우 오른쪽 값을 대입합니다.
return _instance ??= new Logger();
}
}
}
// [8] 로그 기록 메서드입니다. 현재 시간과 함께 메시지를 문자열로 저장합니다.
// "DateTime.Now"는 현재 시간을 가져오며, "AppendLine"은 줄바꿈 포함 추가입니다.
public void Log(string message)
{
_logs.AppendLine($"{DateTime.Now}: {message}");
}
// [9] 지금까지 기록된 로그들을 하나의 문자열로 반환합니다.
// "ToString()"은 StringBuilder에 저장된 전체 문자열을 반환합니다.
public string GetLogs()
{
return _logs.ToString();
}
}
✔️ Logger 테스트
public MainWindow()
{
InitializeComponent();
Logger logger1 = Logger.Instance;
Logger logger2 = Logger.Instance;
logger1.Log("첫 번째 메시지");
logger2.Log("두 번째 메시지");
string v = $"logger1과 logger2는 같은 객체? {object.ReferenceEquals(logger1, logger2)}";
MessageBox.Show(Logger.Instance.GetLogs() + v);
}
//올바른 static 클래스 정의
public static class MathHelper
{
public static int Add(int a, int b)
{
return a + b;
}
public static double Pi = 3.1415;
}
//사용방법
int result = MathHelper.Add(3, 4); // 인스턴스 생성 없이 직접 접근
//컴파일 오류 예: static 클래스에 non-static 멤버 선언
public static class WrongExample
{
public int Count = 0; // ❌ 오류: static 클래스에는 인스턴스 멤버가 있을 수 없음
}
public class CafeInfo
{
// static readonly: 프로그램 시작 시 한 번 초기화되고 이후 변경 불가
public static readonly string CafeBranchCode = "GWANGJU-001";
public static readonly DateTime OpeningTime = DateTime.Now;
public void ShowCafeInfo()
{
Console.WriteLine($"지점 코드: {CafeBranchCode}");
Console.WriteLine($"개점 시간: {OpeningTime}");
}
}
class Program
{
static void Main()
{
var cafe = new CafeInfo();
cafe.ShowCafeInfo();
// ❌ 아래는 불가능: 컴파일 에러 발생
// CafeInfo.CafeBranchCode = \"SEOUL-999\";
}
}
//const 예제
public class AppConfig
{
public const string AppName = "MyApp"; // 컴파일 시 값이 고정됨
public const int MaxUserCount = 100;
}
//static readonly 예제
public class AppConfig
{
public static readonly string StartupTime = DateTime.Now.ToString();
public static readonly int ConfigValue;
static AppConfig()
{
ConfigValue = LoadFromConfig(); // 파일이나 DB 등에서 불러올 수 있음
}
private static int LoadFromConfig() => 42;
}
✔️ 예제 5. Logger 예제에서 Static 사용법 다시 확인하기
// 전역에서 하나만 존재하는 로거 클래스
// Singleton 패턴을 사용하여 인스턴스가 하나만 생성되도록 제한합니다.
public sealed class Logger
{
// static 키워드는 해당 필드가 클래스 단위로 관리되며,
// 프로그램 실행 중 하나만 존재함을 의미합니다.
// null 가능 타입(Logger?)로 초기화되어, 아직 생성되지 않은 상태를 나타냅니다.
private static Logger? _instance = null;
// 동기화 처리를 위한 객체입니다.
// lock 키워드는 여러 스레드가 동시에 접근하는 것을 방지하는데 사용됩니다.
// readonly는 생성 시에만 초기화 가능하며 이후 변경할 수 없습니다.
private static readonly object _lock = new();
// 로그를 누적 저장할 StringBuilder 객체입니다.
// 문자열을 반복해서 더하는 작업에 적합한 자료형입니다.
private readonly StringBuilder _logs = new StringBuilder();
// private 생성자: 외부에서 new Logger()로 생성하지 못하도록 막습니다.
// Singleton 패턴의 핵심으로, 인스턴스를 외부에서 직접 만들 수 없게 합니다.
private Logger()
{
Console.WriteLine("Logger 인스턴스가 생성되었습니다!");
}
// 외부에서 Logger 인스턴스를 사용할 수 있는 유일한 접근 지점
// Instance 프로퍼티를 통해 클래스의 인스턴스를 가져옵니다.
public static Logger Instance
{
get
{
// lock 블록: 한 번에 하나의 스레드만 이 블록을 실행할 수 있게 합니다.
// 이를 통해 멀티스레드 환경에서도 인스턴스가 한 번만 생성되도록 보장합니다.
lock (_lock)
{
// ??= 연산자는 왼쪽 변수가 null일 경우에만 오른쪽 값을 대입합니다.
// 즉, 아직 Logger 인스턴스가 없다면 새로 생성하고, 있으면 기존 값을 반환합니다.
return _instance ??= new Logger();
}
}
}
// Log 메서드는 현재 시간과 함께 로그 메시지를 저장합니다.
// AppendLine은 한 줄씩 로그를 추가하며, 자동으로 줄바꿈을 포함합니다.
public void Log(string message)
{
_logs.AppendLine($"{DateTime.Now:HH:mm:ss} - {message}");
}
// GetLogs 메서드는 지금까지 누적된 로그 전체를 문자열로 반환합니다.
// ToString()은 StringBuilder 내부의 모든 문자열을 하나의 문자열로 반환합니다.
public string GetLogs()
{
return _logs.ToString();
}
}
// 실습용 콘솔 애플리케이션
class Program
{
static void Main(string[] args)
{
// FirstClass에서 로그 남기기
Console.WriteLine("첫 번째 클래스에서 로그를 남깁니다.");
FirstClass.DoSomething();
// SecondClass에서 로그 남기기
Console.WriteLine("\n두 번째 클래스에서도 로그를 남깁니다.");
SecondClass.DoSomethingElse();
// Logger 인스턴스를 통해 전체 로그 출력
Console.WriteLine("\n모든 로그를 출력합니다.");
Console.WriteLine(Logger.Instance.GetLogs());
// logger1 == logger2 검사: 동일 인스턴스인지 확인
Console.WriteLine("\nogger1 == logger2 ? => " +
ReferenceEquals(Logger.Instance, Logger.Instance));
}
}
// 첫 번째 클래스
public class FirstClass
{
// 로그 남기는 작업 메서드
public static void DoSomething()
{
// Logger 인스턴스를 통해 로그 기록
Logger.Instance.Log("FirstClass에서 작업 수행");
}
}
// 두 번째 클래스
public class SecondClass
{
// 로그 남기는 작업 메서드
public static void DoSomethingElse()
{
// Logger 인스턴스를 통해 로그 기록
Logger.Instance.Log("SecondClass에서 다른 작업 수행");
}
}