겨울 세일!

싱글턴 패턴

다음 이름으로도 불립니다: Singleton

의도

싱글턴은 클래스에 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대한 전역 접근​(액세스) 지점을 제공하는 생성 디자인 패턴입니다.

싱글턴 패턴

문제

싱글턴 패턴은 한 번에 두 가지의 문제를 동시에 해결함으로써 을 위반합니다.

  1. 클래스에 인스턴스가 하나만 있도록 합니다. 사람들은 클래스에 있는 인스턴스 수를 제어하려는 가장 일반적인 이유는 일부 공유 리소스​(예: 데이터베이스 또는 파일)​에 대한 접근을 제어하기 위함입니다.

    예를 들어 객체를 생성했지만 잠시 후 새 객체를 생성하기로 했다고 가정해 봅시다. 그러면 새 객체를 생성하는 대신 이미 만든 객체를 받게 됩니다.

    물론 생성자 호출은 특성상 반드시 새 객체를 반환해야 하므로 위 행동은 일반 생성자로 구현할 수 없습니다.

객체에 대한 전역 접근

클라이언트들은 항상 같은 객체와 작업하고 있다는 사실을 인식조차 못 할 수 있습니다.

  1. 해당 인스턴스에 대한 전역 접근 지점을 제공합니다. 필수 객체들을 저장하기 위해 전역 변수들을 정의했다고 가정해 봅시다. 이 변수들을 사용하면 매우 편리할지는 몰라도, 모든 코드가 잠재적으로 해당 변수의 내용을 덮어쓸 수 있고 그로 인해 앱에 오류가 발생해 충돌할 수 있으므로 그리 안전한 방법은 아닙니다.

    전역 변수와 마찬가지로 싱글턴 패턴을 사용하면 프로그램의 모든 곳에서부터 일부 객체에 접근할 수 있습니다. 그러나 이 패턴은 다른 코드가 해당 인스턴스를 덮어쓰지 못하도록 보호하기도 합니다.

    이 문제에는 또 다른 측면이 있습니다. 당신은 첫 번째 문제를 해결하는 코드가 프로그램 전체에 흩어져 있는 것을 원하지 않을 것입니다. 특히 코드의 나머지 부분이 이미 첫 번째 문제를 해결하는 코드에 의존하고 있다면, 이 코드를 한 클래스 내에 두는 것이 훨씬 좋습니다.

최근에는 싱글턴 패턴이 워낙 대중화되어 패턴이 나열된 문제 중 한 가지만 해결하더라도 그것을 이라고 부를 수 있습니다.

해결책

싱글턴의 모든 구현은 공통적으로 다음의 두 단계를 갖습니다.

  • 다른 객체들이 싱글턴 클래스와 함께 new 연산자를 사용하지 못하도록 디폴트 생성자를 비공개로 설정하세요.
  • 생성자 역할을 하는 정적 생성 메서드를 만드세요. 내부적으로 이 메서드는 객체를 만들기 위하여 비공개 생성자를 호출한 후 객체를 정적 필드에 저장합니다. 이 메서드에 대한 그다음 호출들은 모두 캐시된 객체를 반환합니다.

당신의 코드가 싱글턴 클래스에 접근할 수 있는 경우, 이 코드는 싱글턴의 정적 메서드를 호출할 수 있습니다. 따라서 해당 메서드가 호출될 때마다 항상 같은 객체가 반환됩니다.

실제상황 적용

정부는 싱글턴 패턴의 훌륭한 예입니다. 국가는 하나의 공식 정부만 가질 수 있습니다. 그리고 'X의 정부'라는 명칭은 정부를 구성하는 개인들의 신원과 관계없이 정부 책임자들의 그룹을 식별하는 글로벌 접근 지점입니다.

구조

싱글턴 패턴 구조싱글턴 패턴 구조
  1. 싱글턴 클래스는 정적 메서드 get­Instance를 선언합니다. 이 메서드는 자체 클래스의 같은 인스턴스를 반환합니다.

    싱글턴의 생성자는 항상 클라이언트 코드에서부터 숨겨져야 합니다. get­Instance 메서드를 호출하는 것이 Singleton 객체를 가져올 수 있는 유일한 방법이어야 합니다.

의사코드

이 예에서 데이터베이스의 연결 클래스는 싱글턴의 역할을 합니다. 이 클래스에는 공개된 생성자가 없으므로 해당 클래스의 객체를 가져오는 유일한 방법은 get­Instance 메서드를 호출하는 것입니다. 이 메서드는 처음 생성된 객체를 캐시 한 후 모든 후속 호출들에서 해당 객체를 반환합니다.

// 데이터베이스 클래스는 클라이언트들이 프로그램 전체에서 데이터베이스 연결의 같은
// 인스턴스에 접근할 수 있도록 해주는 `getInstance`(인스턴스 가져오기) 메서드를
// 정의합니다.
class Database is
    // 싱글턴 인스턴스를 저장하기 위한 필드는 정적으로 선언되어야 합니다.
    private static field instance: Database

    // 싱글턴의 생성자는 `new` 연산자를 사용한 직접 생성 호출들을 방지하기 위해
    // 항상 비공개여야 합니다.
    private constructor Database() is
        // 데이터베이스 서버에 대한 실제 연결과 같은 일부 초기화 코드.

    // 싱글턴 인스턴스로의 접근을 제어하는 정적 메서드.
    public static method getInstance() is
        if (Database.instance == null) then
            acquireThreadLock() and then
                // 이 스레드가 잠금 해제를 기다리는 동안 인스턴스가 다른
                // 스레드에 의해 초기화되지 않았는지 확인하세요.
                if (Database.instance == null) then
                    Database.instance = new Database()
        return Database.instance

    // 마지막으로 모든 싱글턴은 해당 로직의 인스턴스에서 실행할 수 있는 비즈니스
    // 로직을 정의해야 합니다.
    public method query(sql) is
        // 예를 들어 앱의 모든 데이터베이스 쿼리들은 이 메서드를 거칩니다. 따라서
        // 여기에 스로틀링 또는 캐싱 논리를 배치할 수 있습니다.
        // …

class Application is
    method main() is
        Database foo = Database.getInstance()
        foo.query("SELECT ...")
        // …
        Database bar = Database.getInstance()
        bar.query("SELECT ...")
        // 변수 `bar`는 변수 `foo`와 같은 객체를 포함할 것입니다.

적용

싱글턴 패턴은 당신 프로그램의 클래스에 모든 클라이언트가 사용할 수 있는 단일 인스턴스만 있어야 할 때 사용하세요. 예를 들자면 프로그램의 다른 부분들에서 공유되는 단일 데이터베이스 객체처럼 말입니다.

싱글턴 패턴은 특별 생성 메서드를 제외하고는 클래스의 객체들을 생성할 수 있는 모든 다른 수단들을 비활성화합니다. 이 메서드는 새 객체를 생성하거나 객체가 이미 생성되었으면 기존 객체를 반환합니다.

싱글턴 패턴은 전역 변수들을 더 엄격하게 제어해야 할 때 사용하세요.

전역 변수들과 달리 싱글턴 패턴은 클래스의 인스턴스가 하나만 있도록 보장해 줍니다. 캐시 된 인스턴스는 싱글턴 클래스 자체를 제외하고는 그 어떤 것과도 대체될 수 없습니다.

참고로 이 제한은 언제든 조정할 수 있고 원하는 수만큼의 싱글턴 인스턴스 생성을 허용할 수 있습니다. 그러기 위해서 변경해야 하는 코드의 유일한 부분은 get­Instance 메서드의 본문입니다.

구현방법

  1. 싱글턴 인스턴스의 저장을 위해 클래스에 비공개 정적 필드를 추가하세요.

  2. 싱글턴 인스턴스를 가져오기 위한 공개된 정적 생성 메서드를 선언하세요.

  3. 정적 메서드 내에서 '지연된 초기화'를 구현하세요. 그러면 이것은 첫 번째 호출에서 새 객체를 만든 후 그 객체를 정적 필드에 넣을 것입니다. 이 메서드는 모든 후속 호출들에서 항상 해당 인스턴스를 반환해야 합니다.

  4. 클래스의 생성자를 비공개로 만드세요. 그러면 클래스의 정적 메서드는 여전히 생성자를 호출할 수 있지만 다른 객체들은 호출할 수 없을 것입니다.

  5. 클라이언트 코드를 살펴보며 싱글턴의 생성자에 대한 모든 직접 호출들을 싱글턴의 정적 생성 메서드에 대한 호출로 바꾸세요.

장단점

  • 클래스가 하나의 인스턴트만 갖는다는 것을 확신할 수 있습니다.
  • 이 인스턴스에 대한 전역 접근 지점을 얻습니다.
  • 싱글턴 객체는 처음 요청될 때만 초기화됩니다.
  • 을 위반합니다. 이 패턴은 한 번에 두 가지의 문제를 동시에 해결합니다.
  • 또 싱글턴 패턴은 잘못된 디자인​(예를 들어 프로그램의 컴포넌트들이 서로에 대해 너무 많이 알고 있는 경우)​을 가릴 수 있습니다.
  • 그리고 이 패턴은 다중 스레드 환경에서 여러 스레드가 싱글턴 객체를 여러 번 생성하지 않도록 특별한 처리가 필요합니다.
  • 싱글턴의 클라이언트 코드를 유닛 테스트하기 어려울 수 있습니다. 그 이유는 많은 테스트 프레임워크들이 모의 객체들을 생성할 때 상속에 의존하기 때문입니다. 싱글턴 클래스의 생성자는 비공개이고 대부분 언어에서 정적 메서드를 오버라이딩하는 것이 불가능하므로 싱글턴의 한계를 극복할 수 있는 창의적인 방법을 생각해야 합니다. 아니면 그냥 테스트를 작성하지 말거나 싱글턴 패턴을 사용하지 않으면 됩니다.

다른 패턴과의 관계

  • 대부분의 경우 하나의 퍼사드 객체만 있어도 충분하므로 퍼사드 패턴의 클래스는 종종 싱글턴으로 변환될 수 있습니다.

  • 만약 객체들의 공유된 상태들을 단 하나의 플라이웨이트 객체로 줄일 수 있다면 플라이웨이트싱글턴과 유사해질 수 있습니다. 그러나 이 패턴들에는 두 가지 근본적인 차이점이 있습니다:

    1. 싱글턴은 인스턴스가 하나만 있어야 합니다. 반면에 클래스는 여러 고유한 상태를 가진 여러 인스턴스를 포함할 수 있습니다.
    2. 객체는 변할 수 있습니다 (mutable). 플라이웨이트 객체들은 변할 수 없습니다 (immutable).
  • 추상 팩토리들, 빌더들프로토타입들은 모두 싱글턴으로 구현할 수 있습니다.

코드 예시

C#으로 작성된 싱글턴 C++로 작성된 싱글턴 Go로 작성된 싱글턴 자바로 작성된 싱글턴 PHP로 작성된 싱글턴 파이썬으로 작성된 싱글턴 루비로 작성된 싱글턴 러스트로 작성된 싱글턴 스위프트로 작성된 싱글턴 타입스크립트로 작성된 싱글턴