싱글턴 패턴
의도
싱글턴은 클래스에 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대한 전역 접근(액세스) 지점을 제공하는 생성 디자인 패턴입니다.
문제
싱글턴 패턴은 한 번에 두 가지의 문제를 동시에 해결함으로써 단일 책임 원칙을 위반합니다.
-
클래스에 인스턴스가 하나만 있도록 합니다. 사람들은 클래스에 있는 인스턴스 수를 제어하려는 가장 일반적인 이유는 일부 공유 리소스(예: 데이터베이스 또는 파일)에 대한 접근을 제어하기 위함입니다.
예를 들어 객체를 생성했지만 잠시 후 새 객체를 생성하기로 했다고 가정해 봅시다. 그러면 새 객체를 생성하는 대신 이미 만든 객체를 받게 됩니다.
물론 생성자 호출은 특성상 반드시 새 객체를 반환해야 하므로 위 행동은 일반 생성자로 구현할 수 없습니다.
-
해당 인스턴스에 대한 전역 접근 지점을 제공합니다. 필수 객체들을 저장하기 위해 전역 변수들을 정의했다고 가정해 봅시다. 이 변수들을 사용하면 매우 편리할지는 몰라도, 모든 코드가 잠재적으로 해당 변수의 내용을 덮어쓸 수 있고 그로 인해 앱에 오류가 발생해 충돌할 수 있으므로 그리 안전한 방법은 아닙니다.
전역 변수와 마찬가지로 싱글턴 패턴을 사용하면 프로그램의 모든 곳에서부터 일부 객체에 접근할 수 있습니다. 그러나 이 패턴은 다른 코드가 해당 인스턴스를 덮어쓰지 못하도록 보호하기도 합니다.
이 문제에는 또 다른 측면이 있습니다. 당신은 첫 번째 문제를 해결하는 코드가 프로그램 전체에 흩어져 있는 것을 원하지 않을 것입니다. 특히 코드의 나머지 부분이 이미 첫 번째 문제를 해결하는 코드에 의존하고 있다면, 이 코드를 한 클래스 내에 두는 것이 훨씬 좋습니다.
최근에는 싱글턴 패턴이 워낙 대중화되어 패턴이 나열된 문제 중 한 가지만 해결하더라도 그것을 싱글턴이라고 부를 수 있습니다.
해결책
싱글턴의 모든 구현은 공통적으로 다음의 두 단계를 갖습니다.
- 다른 객체들이 싱글턴 클래스와 함께
new
연산자를 사용하지 못하도록 디폴트 생성자를 비공개로 설정하세요. - 생성자 역할을 하는 정적 생성 메서드를 만드세요. 내부적으로 이 메서드는 객체를 만들기 위하여 비공개 생성자를 호출한 후 객체를 정적 필드에 저장합니다. 이 메서드에 대한 그다음 호출들은 모두 캐시된 객체를 반환합니다.
당신의 코드가 싱글턴 클래스에 접근할 수 있는 경우, 이 코드는 싱글턴의 정적 메서드를 호출할 수 있습니다. 따라서 해당 메서드가 호출될 때마다 항상 같은 객체가 반환됩니다.
실제상황 적용
정부는 싱글턴 패턴의 훌륭한 예입니다. 국가는 하나의 공식 정부만 가질 수 있습니다. 그리고 'X의 정부'라는 명칭은 정부를 구성하는 개인들의 신원과 관계없이 정부 책임자들의 그룹을 식별하는 글로벌 접근 지점입니다.
구조
-
싱글턴 클래스는 정적 메서드
getInstance
를 선언합니다. 이 메서드는 자체 클래스의 같은 인스턴스를 반환합니다.싱글턴의 생성자는 항상 클라이언트 코드에서부터 숨겨져야 합니다.
getInstance
메서드를 호출하는 것이 Singleton 객체를 가져올 수 있는 유일한 방법이어야 합니다.
의사코드
이 예에서 데이터베이스의 연결 클래스는 싱글턴의 역할을 합니다. 이 클래스에는 공개된 생성자가 없으므로 해당 클래스의 객체를 가져오는 유일한 방법은 getInstance
메서드를 호출하는 것입니다. 이 메서드는 처음 생성된 객체를 캐시 한 후 모든 후속 호출들에서 해당 객체를 반환합니다.
적용
싱글턴 패턴은 당신 프로그램의 클래스에 모든 클라이언트가 사용할 수 있는 단일 인스턴스만 있어야 할 때 사용하세요. 예를 들자면 프로그램의 다른 부분들에서 공유되는 단일 데이터베이스 객체처럼 말입니다.
싱글턴 패턴은 특별 생성 메서드를 제외하고는 클래스의 객체들을 생성할 수 있는 모든 다른 수단들을 비활성화합니다. 이 메서드는 새 객체를 생성하거나 객체가 이미 생성되었으면 기존 객체를 반환합니다.
싱글턴 패턴은 전역 변수들을 더 엄격하게 제어해야 할 때 사용하세요.
전역 변수들과 달리 싱글턴 패턴은 클래스의 인스턴스가 하나만 있도록 보장해 줍니다. 캐시 된 인스턴스는 싱글턴 클래스 자체를 제외하고는 그 어떤 것과도 대체될 수 없습니다.
참고로 이 제한은 언제든 조정할 수 있고 원하는 수만큼의 싱글턴 인스턴스 생성을 허용할 수 있습니다. 그러기 위해서 변경해야 하는 코드의 유일한 부분은 getInstance
메서드의 본문입니다.
구현방법
-
싱글턴 인스턴스의 저장을 위해 클래스에 비공개 정적 필드를 추가하세요.
-
싱글턴 인스턴스를 가져오기 위한 공개된 정적 생성 메서드를 선언하세요.
-
정적 메서드 내에서 '지연된 초기화'를 구현하세요. 그러면 이것은 첫 번째 호출에서 새 객체를 만든 후 그 객체를 정적 필드에 넣을 것입니다. 이 메서드는 모든 후속 호출들에서 항상 해당 인스턴스를 반환해야 합니다.
-
클래스의 생성자를 비공개로 만드세요. 그러면 클래스의 정적 메서드는 여전히 생성자를 호출할 수 있지만 다른 객체들은 호출할 수 없을 것입니다.
-
클라이언트 코드를 살펴보며 싱글턴의 생성자에 대한 모든 직접 호출들을 싱글턴의 정적 생성 메서드에 대한 호출로 바꾸세요.
장단점
- 클래스가 하나의 인스턴트만 갖는다는 것을 확신할 수 있습니다.
- 이 인스턴스에 대한 전역 접근 지점을 얻습니다.
- 싱글턴 객체는 처음 요청될 때만 초기화됩니다.
- 단일 책임 원칙을 위반합니다. 이 패턴은 한 번에 두 가지의 문제를 동시에 해결합니다.
- 또 싱글턴 패턴은 잘못된 디자인(예를 들어 프로그램의 컴포넌트들이 서로에 대해 너무 많이 알고 있는 경우)을 가릴 수 있습니다.
- 그리고 이 패턴은 다중 스레드 환경에서 여러 스레드가 싱글턴 객체를 여러 번 생성하지 않도록 특별한 처리가 필요합니다.
- 싱글턴의 클라이언트 코드를 유닛 테스트하기 어려울 수 있습니다. 그 이유는 많은 테스트 프레임워크들이 모의 객체들을 생성할 때 상속에 의존하기 때문입니다. 싱글턴 클래스의 생성자는 비공개이고 대부분 언어에서 정적 메서드를 오버라이딩하는 것이 불가능하므로 싱글턴의 한계를 극복할 수 있는 창의적인 방법을 생각해야 합니다. 아니면 그냥 테스트를 작성하지 말거나 싱글턴 패턴을 사용하지 않으면 됩니다.
다른 패턴과의 관계
-
대부분의 경우 하나의 퍼사드 객체만 있어도 충분하므로 퍼사드 패턴의 클래스는 종종 싱글턴으로 변환될 수 있습니다.
-
만약 객체들의 공유된 상태들을 단 하나의 플라이웨이트 객체로 줄일 수 있다면 플라이웨이트는 싱글턴과 유사해질 수 있습니다. 그러나 이 패턴들에는 두 가지 근본적인 차이점이 있습니다:
- 싱글턴은 인스턴스가 하나만 있어야 합니다. 반면에 플라이웨이트 클래스는 여러 고유한 상태를 가진 여러 인스턴스를 포함할 수 있습니다.
- 싱글턴 객체는 변할 수 있습니다 (mutable). 플라이웨이트 객체들은 변할 수 없습니다 (immutable).