단 하나뿐인 특별함
프로그래밍을 처음 배우는 사람들에게 디자인 패턴이라는 말은 조금 낯설게 느껴질 수 있습니다. 하지만 우리 주변을 둘러보면, 이런 패턴들은 일상에서 흔히 발견할 수 있어요.
1990년 초반은 지금처럼 고사양의 컴퓨터가 아니었습니다. 메모리가 1mb도 되지 않았죠. 이런 제한적인 상황에서 같은 객체(현실 세계의 사물이나 개념을 코드로 표현한 것입니다.)를 계속 만든다는 건 효율적이지 않았습니다.
또한, 기존의 전역 변수는 많은 문제를 일으켰습니다. 누구나 전역 변수를 수정할 수 있어서 데이터의 일관성이 없었죠. 전역 변수는 프로그램 내에서 공통으로 사용되는 변수입니다. 가령, 내 폰의 설정한 이름 같은 겁니다. 앱이 설치될 때마다 내 폰의 이름이 바뀌어 버리면 곤란하죠.
프린터나 데이터베이스 연결 같은 공유 자원을 관리해야 하는데 여러 곳에서 접근할 수 있으면서도 일관성을 유지해야 했죠. 네트워크가 발달되면서 이러한 문제는 더 크게 나타났습니다. 프린터 한 대를 공유했는데, 누구나 프린터 설정을 바꾸어 버리면 안 됩니다. 그러다 프린터가 맛이 갑니다.
이런 문제들을 해결하기 위해 GoF는 싱글톤 패턴을 다음과 같은 특징을 가진 해결책으로 제시했습니다:
객체가 오직 하나만 존재하도록 보장
이 객체에 대한 전역적인 접근점 제공
객체 생성을 해당 클래스가 직접 제어
하지만 흥미로운 점은, GoF의 Erich Gamma가 후에 인터뷰에서 "만약 디자인 패턴 책을 다시 쓴다면, 싱글톤은 패턴 목록에서 제외할 것"이라고 말했다는 것입니다. 이는 싱글톤이 전역 상태를 만들어내는 문제와, 단위 테스트를 어렵게 만드는 등의 단점이 있기 때문이었습니다.
이처럼 싱글톤 패턴은 특정한 문제를 해결하기 위해 만들어졌지만, 시간이 지나면서 그 사용에 대한 다양한 의견이 제시되었고, 지금도 여전히 논란이 있는 패턴 중 하나입니다.
'싱글톤 패턴'은 우리 주변에서 쉽게 찾아볼 수 있는 개념입니다. 한 학교에 교장 선생님은 한 분이고, 한 나라의 대통령도 한 명이며, 우리 집 대문 키도 하나입니다. 이처럼 '단 하나'만 존재해야 하는 것들이 있죠.
프로그래밍에서도 마찬가지입니다. 어떤 프로그램에서는 특정 객체가 단 하나만 존재해야 하는 경우가 있습니다. 가령 회사의 프린터 시스템을 생각해 봅시다. 한 대의 프린터를 여러 사람이 사용할 때, 이를 관리하는 시스템은 하나여야 합니다. 만약 여러 개의 관리 시스템이 있다면 어떻게 될까요? 아마도 인쇄 순서가 뒤죽박죽 되고, 설정이 꼬이고, 결국 큰 혼란이 발생할 겁니다.
// 프린터라는 객체를 하나 만듭니다.
public class Printer {
public Printer() {
// 프린터 초기화
}
public void print(Document doc) {
// 인쇄 처리
}
}
// 여러 곳에서 각각 다른 프린터 객체를 생성
Printer printer1 = new Printer();
Printer printer2 = new Printer();
Printer printer3 = new Printer();
// 이렇게 되면 프린터를 관리하는 객체는 한 개인데, 3군데에서 각각 프린터를 관리하는 것과 같아서, 순서나 설정이 뒤죽박죽 될 수도 있습니다.
// 사장이라는 직책이 있는데, 3명이서 직책을 가지고 있다고 보시면 됩니다. 누구의 말과 결정을 들을지 애매한 사항이 발생할 수 있습니다.
이런 혼란을 막기 위해 우리는 싱글톤 패턴을 사용합니다. 마치 한 회사에 사장이 한 명인 것처럼, 프로그램 안에서도 특정 역할을 하는 객체를 단 하나만 만들어 사용하는 것이죠.
도서관을 예로 들어볼까요? 도서관에는 수많은 책이 있지만, 도서 대출 시스템은 하나입니다. 만약 도서 대출 시스템이 여러 개라면 어떻게 될까요? 한 시스템에서는 책이 대출 가능하다고 하고, 다른 시스템에서는 이미 대출됐다고 할 수도 있겠죠. 이런 혼란을 막기 위해 도서관은 하나의 통합된 시스템을 사용합니다.
싱글톤 패턴도 이와 같은 원리입니다. 프로그램 전체에서 공유해야 하는 자원이나 설정을 관리할 때, 우리는 그것을 관리하는 객체를 단 하나만 만들어 사용합니다. 데이터베이스 연결, 환경 설정, 로그 기록 등이 대표적인 예시죠.
public class Printer {
// 단 하나의 프린터 인스턴스를 설정합니다.
private static Printer instance;
// 외부에서 new Printer()로 여러 개 생성하지 못하도록 막습니다.
private Printer() {}
// 오직 이 메서드로만 프린터 객체를 얻을 수 있음
public static Printer getInstance() {
if (instance == null) {
instance = new Printer();
}
return instance;
}
public void print(Document doc) {
// 인쇄 처리
}
}
// 어디서 호출하든 같은 프린터 객체를 사용합니다.
Printer printer = Printer.getInstance();
printer.print(myDocument);
하지만 모든 것이 그렇듯, 싱글톤 패턴에도 주의할 점이 있습니다. 마치 한 나라의 대통령에게 너무 많은 권한이 집중되면 문제가 생길 수 있는 것처럼, 프로그램에서도 싱글톤 객체에 너무 많은 책임을 부여하면 문제가 발생할 수 있습니다.
또한 여러 사람이 동시에 이용하는 상황도 고려해야 합니다. 마치 은행의 ATM기처럼, 여러 사람이 동시에 접근할 때는 순서와 규칙이 필요하죠. 프로그래밍에서는 이를 '동기화'라고 부릅니다.
public class Printer {
private static volatile Printer instance;
private Printer() {}
public static Printer getInstance() {
if (instance == null) {
// 동시에 만들려고 했을 경우 'synchronized 동기화'를 통해 하나를 우선 만들고 만든 것을 주는 겁니다. (대기줄이라고 생각하시면 됩니다.)
synchronized (Printer.class) {
if (instance == null) {
instance = new Printer();
}
}
}
return instance;
}
}
싱글톤 패턴은 단순히 기술적인 해결책이 아닙니다. 이는 '단 하나'라는 특별함의 가치를 프로그래밍적으로 구현한 것이라고 할 수 있죠. 우리 일상에서 단 하나뿐인 것들이 가지는 의미와 가치를 생각해 보면, 싱글톤 패턴의 중요성을 더 잘 이해할 수 있을 것입니다.
프로그래밍의 세계에서도, 때로는 '하나'가 주는 단순함과 명확함이 필요합니다.