디자인 패턴

싱글톤 패턴(Singleton Pattern)

내일도무사히 2021. 10. 28. 20:58

💡 싱글톤 패턴이란?

GOF의 디자인 패턴중 '생성' 패턴에 속하는 패턴으로, 클래스에서 오직 '한 개'의 인스턴스만 인스턴스 화 할 수 있도록 규제하는 디자인 패턴

 

📗 싱글톤 패턴의 장점

  1. 클래스가 오직 한 개의 인스턴스만 가지고 있다고 확신 할 수 있다.
  2. 인스턴스 수를 제한 할 수 있다.
  3. 전역 변수에 액세스 함으로써 손쉽게 접근 할 수 있다.

 

📖 함께 쓰일 수 있는 패턴들

  1. Abstract pattern, Factory Method, Builder, Prototype
  2. State pattern, Facade Pattern

 

📖 전역변수보다 싱글톤 패턴이 나은 점

  1. 불필요한 변수로 Global namespace를 훼손하지 않는다.
  2. 지연 할당과 초기화를 지원함으로써 사용하든 안하든 리소스를 소모하는 전역변수와 달리 싱글턴 패턴은 효율적으로 자원 활용이 가능하다.

 

⚠️ 싱글톤 패턴의 단점

  • 싱글톤 패턴은 안티 패턴에 속한다. 안티패턴이란 프로그램을 만들 때 비효율적이고 역효과가 나타날 수 있는 패턴을 말한다.
  • 싱글톤 패턴은 주로 global하게 선언되는데, 이는 프로그램의 결합도를 증가시켜 유닛 테스트를 하기 힘들게하고, 싱글톤 객체에 의존하는 객체가 불필요한 제약을 따르게 한다.
  • 싱글톤 패턴을 사용하면, 싱글톤 패턴을 적용한 객체는, 객체를 오직 '하나'만 생성해야 하는 규약과 자신이 원래 하던 일 두가지를 하게 되므로 SRP(Single responsible Principle)도 위반한다.

 

🚀 구현

일반적으로 싱글턴은 클래스의 모든 '생성자'를 private으로 선언하고, 생성된 객체를 참조할 수 있는 변수를 리턴하는 static 메소드를 사용하는 방식으로 구현된다.

class SingletonTest {

    @Test
    void singletonTest() {
        SingletonObject singletonObject1 = SingletonObject.getInstance();
        SingletonObject singletonObject2  = SingletonObject.getInstance();
        System.out.println(singletonObject1 == singletonObject2);
    }
}

class SingletonObject {

    private static SingletonObjectinstance;

    private SingletonObject() {}

    public static SingletonObject getInstance() {
        if(instance== null) {
                    instance= new SingletonObject();
        }

        return instance;
    }
}

output

 

 

멀티 스레드 환경에서 Race Condition을 막기 위해서 다음과 같이 구현 할 수도 있다.

class SingletonTest {

    @Test
    void singletonTest() {
        SingletonObject singletonObject1 = SingletonObject.getInstance();
        SingletonObject singletonObject2  = SingletonObject.getInstance();
        System.out.println(singletonObject1 == singletonObject2);
    }
}

class SingletonObject {

    private static volatile SingletonObject instance;

    private SingletonObject(){};

    public static SingletonObject getInstance() {
        if(instance == null) {
            synchronized (SingletonObject.class) {
                if(instance == null) {
                    instance = new SingletonObject();
                }
            }
        }

        return instance;
    }
}

output2

 

위의 코드에서 'volatile'과 'synchronized '키워드가 어색해서 검색해봤다.

volatile은 변수의 값을 '캐시'가 아닌 메인 메모리에 저장하라는 것이고 synchronized 키워드는 멀티 스레드 환경에서 race condition을 막기 위해 사용하는 것으로, 한 스레드가 synchronized 메소드에 들어가면 'lock'을 걸어 다른 스레드가 들어오지 못하게 하는 메소드라고 한다. 둘 다 멀티스레드 환경에서 읽기/쓰기를 할 경우 Race Condition을 방지하기 위해 사용하는 메소드인듯하다.