Lee's Grow up

[디자인패턴/Design Pattern] Singleton 패턴 / 싱글톤 패턴 본문

PROGRAMMING/디자인패턴

[디자인패턴/Design Pattern] Singleton 패턴 / 싱글톤 패턴

효기로그 2019. 11. 26. 09:49
반응형

관련 내용은 [자바 언어로 배우는 디자인 패턴 입문] 이라는 책의 공부 내용을 개인적으로 정리한 내용입니다.
처음 배우는 부분이기 때문에 틀린 부분이 있다면 지적해주시면 감사하겠습니다.
또한 관련 내용은 Effective Java의 내용도 포함되어 있습니다.

1. Singleton 패턴이란?


생성자가 여러 차례 호출되어도 실제 생성되는 객체는 최초의 1개이고, 그 이후의 생성자는 최초 생성자를 통해 생성한 객체를 리턴하는 방식입니다. 즉, 인스턴스가 1개 밖에 존재하지 않는 것을 보증하는 패턴입니다.

2. Singleton 패턴의 등장 인물


이번 포스팅에서 사용될 요소들의 역할입니다.

  • Singleton의 역할
    1. 유일한 인스턴스를 얻기 위한 static메소드를 가지며. 이 메소드는 언제나 동일한 인스턴스를 반환하는 역할
Singleton 패턴의 클래스 다이어그램

3. 예제


이제 Singleton의 예제로 내용을 구체화 해보겠습니다. 다만 Singleton 방식에 대해서 책의 내용은 입문 책이라 그런지 가장 고전적인 방법을 안내해주고 있어서 추가로 내용을 정리해서 올립니다.

3-1. 이 책에서 소개하는 Singleton 패턴

가장 기본적인 singleton 패턴 방식이며, 아래 내용을 이해하기 위한 기본 구조입니다. 하지만 환경에 따라 여러 문제점이 발생하기 때문에 해결법은 아래에서 추가로 설명하겠습니다.

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton() {
        System.out.println("인스턴스를 생성했습니다.");
    }
    public static Singleton getInstance() {
        return singleton;
    }
}
문제점

위와 같은 방식으로 Singleton정적 팩토리 방식으로 생성하면, 간결하다는 장점이 존재한니다.
하지만 Reflection에 의해서 객체의 인스턴스는 단 1개만 보장한다는 방식이 파괴되며, 직렬화 과정에서 같은 객체라는 보장이 되지 않습니다. 아래 예시에서 Reflection에 대한 예시를 설명하겠습니다. 직렬화에 대한 테스트는 독자분들에게 맡기겠습니다.

싱글톤 파괴
public class Main {
    public static void main(String[] args) throws Exception {

        StaticFactorySingleton singleton1 = StaticFactorySingleton.getInstance();
        System.out.println(singleton1.hashCode());

        Constructor cs = StaticFactorySingleton.class.getDeclaredConstructor();
        cs.setAccessible(true);

        StaticFactorySingleton singleton2 = (StaticFactorySingleton)cs.newInstance();
        System.out.println(singleton3.hashCode());
    }
}

위와 같이 실행을 하게 되면 일반적인 호출로는 같은 인스턴스를 반환해주지만, reflection을 사용한 객체는 전혀 새로운 객체를 반환해줍니다. 

실행 결과
인스턴스를 생성했습니다.
5433634
인스턴스를 생성했습니다.
2430287

3-2. EFFECTIVE Java에서 소개하는 방법

해당 방식전에 Singleton을 보장( 멀티쓰레드, 직렬화 ) 하기 위한 방식으로 Double Checked Locking 방식이 존재하지만, 현재 사용하지 않는다고 해서 설명은 생략하지만 관련 글을 읽어보면 좋을거 같아서 링크를 남깁니다.

https://medium.com/@kevalpatel2106/how-to-make-the-perfect-singleton-de6b951dfdb0

 

How to make the perfect Singleton?

Design Patterns are popular among software developers. A design pattern is a well described solution to a common software problem. The…

medium.com

그러면, 본론으로 들어와서 EFFECTIVE Java에서 소개하는 방식을 소개하겠습니다.
내용은 간단합니다. singleton을 사용할 객체를 class가 아닌 enum으로 정의하는 방식입니다. 코드는 아래와 같습니다.

public enum Singleton {
    INSTANCE;
}

위와 같은 방식은 우선 간결하고, 추가의 노력 없이 직렬화가 가능하고, 심지어 멀티쓰레드에서도 안정적입니다. 또한 리플렉션으로 생성자에 접근하지도 못합니다.
하지만 만들려는 SingletonEnum 외의 클래스를 상속해야 하거나, java 1.5이하 버전이라든지의 상황에서는 사용이 불가능합니다. 또한 메모리에 대한 성능 이슈도 존재합니다.

3-3. Initialization-on-demand holder idiom 방식

enum 방식은 안드로이드 개발시에 문제가 발생할 수 있어서 해당 방식이 제일 많이 사용하는 방식입니다. 이 방식을 사용하면, 위 본문에 소개에서 생략한 Double Checked Locking 과 같은 방식에서 사용한 synchronized도 필요없어서 성능이 뛰어나고, enum방식처럼 Java 버전에 상관이 없습니다. 코드는 아래와 같습니다.

public class Singleton{
    private Singleton() { }
    public static Singleton getInstance() { 
        return LazyHolder.INSTANCE;
    }

    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

해당 방식에 대한 설명은 간단히 하자면, 객체가 필요로 할때 초기화를 미뤄서, 불필요하게 메모리를 잡아먹지 않습니다.
Singleton 클래스를 로드를 해도 LazyHolder 클래스의 변수가 없기 때문에 메모리에 할당받지 않습니다.
이는 자바 클래스로더 매커니즘을 이용한 방식으로, SingletongetInstance() 메소드를 호출하는 순간 LazyHolder Class가 로딩됩니다.
이 때 Class를 로딩하고, 초기화하는 시점에 JVM에 의해서 thread-safe를 보장하기 때문에, 불필요한 키워드로 인한 성능저하가 없기 때문에 실무에서 가장 많이 사용하는 방식이라고 합니다.
그러나, 다른 방식과 마찬가지로 Reflection을 통해 파괴가 가능합니다.

4.마치며


위와 같으 Singleton을 만드는 방법은 다양하며, 각각 장단점이 존재하니 상황에 따라 필요한 방식으로 선택하면 될 것 같습니다.

5. 관련 패턴


아래 4개는 인스턴스가 1개인 경우가 많이 존재합니다.

  1. Abstract Factory 패턴
  2. Builder 패턴
  3. Facade 패턴
  4. Prototype 패턴 link
반응형
Comments