티스토리 뷰

knowledge

[Pattern][Java][Python] Singleton 생성 패턴에 대하여

글을 쓰는 개발자 2021. 11. 4. 19:58
반응형

1. GoF 디자인 패턴

의도

오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공

활용성

1. 클래스의 인스턴스가 오직 하나여야 함을 보장하고, 잘 정의된 접근점으로 모든 사용자가 접근할 수 있도록 해야 할 때
2. 유일한 인스턴스가 서브클래싱으로 확장되어야 하며, 사용자는 코드의 수정없이 확장된 서브클래스의 인스턴스를 사용할 수 있어야 할 때

결과

1. 유일하게 존재하는 인스턴스로의 접근을 통제한다.
2. 이름 공간을 좁힌다.
3. 연산 및 표현의 정제를 허용
4. 인스턴스의 개수를 변경하기가 자유롭다.
5. 클래스 연산을 사용하는 것보다 훨씬 유연한 방법

 

2. Java Effective

private 생성자나 열거 타입으로 싱글턴임을 보증하라(아이템3)
싱글턴을 만드는 방식은 보통 둘 중 하나인데 공통적으로 생성자를 private으로 감춰두고, 유일한 인스턴스에 접근하는 수단으로
1. pulic static 멤버를 마련
2. 정적 팩터리 메서드를 public static 멤버로 제공

그 이외 마지막으로
3. Enum을 선언하는 방식

 

3. 방식들

 

1. Eager Initialization

가장 기본적인 싱글턴 방식

전역변수로 'instance'를 만드는데 private static을 사용한다. 자바 이팩티브에서 말하는 2번째 방법이기도 하다. 

생성자에 private 접근제어자를 설정함으로써 'new' 키워드를 사용 못하게 하고, static method를 통해 객체를 받는 방법으로 singleton을 구현하는 방법이다.

 

public class SingleTon {
	private static final SingleTon instance = new SingleTon();
    private SingleTon(){}
    public static SingleTon getInstance(){
    	return instance;
    }
}

위의 방식 싱글턴의 단점은 프로그램의 크기가 커져서 수 많은 클래스에서 위와 같은 singleton pattern 방식을 쓴다고 했을 때 모든 클래스들이 load되는 시점에 과부하가 일어나 엄청난 부하를 줄 수가 있다는 단점이 있다. 그 뿐만 아니라 인스턴스화 되는 시점에 어떠한 에러처리도 할 수가 없다.

 

1-2 자바 이팩티브 첫 번째 방법

이 방법은 eager initialization 방법과 유사해서 같이 첨부하겠습니다.

public class SingleTon{
	public static final SingleTon instance = new SingleTon();
    private SingleTon() {}
}

차이점이 있다면 첫 번쩨 방식은 정적 팩터리 메서드와 병합한 방식이고 위의 방식은 변수로 사용하는 방식이다.

 

1-2-2 static block initialization

public class SingleTon {
   private static SingleTon instance;
   private SingleTon() {}

    static {
       try{
           instance = new SingleTon();
       }catch (Exception e){
           throw new RuntimeException("싱글톤 클래스를 생성하는데 실패!");
       }
    }
    
    public static SingleTon getInstance(){
       return instance;
    }
}

static 초기화 블럭을 이용하면 클래스가 로딩될 때 최초 한 번 실행하게 된다. 하지만 최초 로딩시 바로 가져온다는 점에서 여전히 문제점을 가지고 있다.

1-3 python version( 위에 있는 1-2 방법과 같은 방법이다.)

class Singleton:
    __instance = None

    def __repr__(self):
        return '싱글톤이지.'

    @classmethod
    def __getInstance(cls):
        return cls.__instance

    @classmethod
    def instance(cls, *args, **kwargs):
        cls.__instance = cls(*args, **kwargs)   # 생성자
        cls.instance = cls.__getInstance    # instance = new SingleTon() 자바 부분과 유사
        return cls.__instance

 

 

2. Lazy initialization

제목만 봐도 아시다시피 바로 로드되는 것이 아닌 사용시점에 로드되는 방식입니다.

 

public class SingleTon {
    private static SingleTon instance;
    private SingleTon() {}

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

}

어디서 인스턴스화를 하는 지 보시면 메소드 안에서 이루어져 있는 것을 보실 수 있습니다. 해당 방법은 로드되었을 때 바로 생성되지 않는 것에서 부담을 많이 줄일 수 있습니다. 하지만 이 방식은 멀티 쓰레드 방식에서 여러 개의 쓰레드 중 동일하게 if문을 접근 했을 때 하나가 아닌 여러 개의 인스턴스가 생길 수 있는 문제점이 있습니다.

 

2-2 thread safe initalization 

위의 문제를 해결하기 위해 'synchronized' 키워드를 적용하여 동기화를 적용하면 동시 접근에 대한 문제점을 피할 수 있다. 하지만 동기화 작업을 진행하게 되면 성능저하가 일어나는 것은 불가피하게 된다.

 

public class SingleTon {
    private static SingleTon instance;
    private SingleTon() {}

    public static synchronized SingleTon getInstance() {
        if (instance == null){
            instance = new SingleTon();
        }
        return instance;
    }

}

 

 

2-3 파이썬 lazy singleton - 데코레이터 방식

from functools import wraps


def singleton(func):
    instances = {}

    @wraps(func)
    def get_instance(*args, **kwargs):
        if func not in instances:
            instances[func] = func(*args, **kwargs)
        return instances[func]

    return get_instance


@singleton
class SingleTon(object):
    """
    싱글톤 연습중~~(wraps 는 docs 나 클래스에 대한 정보를 가져올 때 필요한다고 합니다.)
    """
    def __repr__(self):
        return '싱글톤_데코레이터_이지'


a = SingleTon()
b = SingleTon()
print(a)
print(SingleTon.__doc__)
print(id(a))
print(id(b))
"""
싱글톤_데코레이터_이지
싱글톤 연습중~~(wraps 는 docs 나 클래스에 대한 정보를 가져올 때 필요한다고 합니다.)
139691629801256
139691629801256
"""

새로 호출하여도 id() 값이 같은 것을 알 수 있다.

 

2-3-2 데코레이터와 비슷한 버전이지만 클래스를 상속하여 구현하는 방법( 데코레이터 형식보다 느리다고 함)

class Singleton(object):
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not isinstance(cls._instance, cls):
            cls._instance = object.__new__(cls, *args, **kwargs)
        return cls._instance


class AdoptSingleTon(Singleton, object):
    def __repr__(self):
        return '싱글 톤 적용한 모습'


a = AdoptSingleTon()
b = AdoptSingleTon()
print(id(a))
print(id(b))
print(a)

"""
139687726617824
139687726617824
싱글 톤 적용한 모습
"""

 

3. initialization on demand holder idiom

jvm의 class loader 매커니즘과 class의 load 시점을 이용하여 내부 class를 생성시킴으로 thread간의 동기화 문제를 해결

public class SingleTon {

    private SingleTon() {}

    private static class InnerSingleTon {
        private static final SingleTon instance = new SingleTon();
    }

    public static SingleTon getInstance() {
        return InnerSingleTon.instance;
    }

}

이 방법 또한 lazy initialization 이며  모든 자바 버전에서 사용가능하다. 현재 자바에서 싱글톤을 생성시킨다고 하면 거의 위와 같은 방식으로 사용한다고 한다.

 

3-2 파이썬 버전

파이썬에서 metaclass로 생성하는 방식이 있다. 파이썬은 모든 것이 객체다 보니 위와 같은 방식과는 조금 의미가 다를 수는 있지만 metaclass로 흔히 우리가 생각하는 클래스(설계도)를 만들고 이를 적용할 클래스(객체)에 적용하면 된다.

참고: https://alphahackerhan.tistory.com/34 

사전 지식:

메타클래스를 만드는 방법은 'type'을 활용 

인스턴스를 생성하는 방식은 __new__ -> __init__ -> __call__

메타클래스를 상속받은 클래스를 생성했을 때 일어나는 일은

메타클래스 __new__
메타클래스 __init__
============ 객체 생성 전 =============
메타클래스 __call__
객체 __init__

와 같이 일어나게 된다.

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class AdoptSingleTon(object, metaclass=Singleton):
    def __repr__(self):
        return '나는 메타클래스로 구현한 싱글톤이지'

위와 같이 메타클래스(설계도)에 생성했을 때 일어나야 하므로 __call__ 부분에 정의하면 된다.(lazy initialization을 하기 위해서)

그러면 객체 생성 했을 때 메타 클래스의 __call__ 부분을 타면서 생성하지 않았을 때에는 생성을 하고 이미 생성을 했다면 기존의 것을 반환한다.(기본적으로 파이썬은 싱글 쓰레드로 동작하기 때문에 thread에 대해서 크게 걱정할 필요가 없을 것 같다. 혹시 자세히 아시는 분이 있다면 댓글로 알려주시면 감사하겠습니다.)

 

3-2-2  metaclass decorator version

from functools import wraps


def singleton(class_):
    class Meta(class_):
        _instance = None

        def __new__(cls, *args, **kwargs):
            if Meta._instance is None:
                Meta._instance = super(Meta, cls).__new__(cls, *args, **kwargs)
                Meta._instance._sealed = False  # init 들어가기 전에 변수 선언
            return Meta._instance

        def __init__(self, *args, **kwargs):
            if self._sealed:  # 변수가 최초 선언이 이미 되었다면
                return
            super(Meta, self).__init__(*args, **kwargs)
            self._sealed = True  # 변수 최초 선언이 되었으면 봉인한다.

    Meta.__name__ = class_.__name__
    return Meta


@singleton
class MyClass(object):
    pass


a = MyClass()
b = MyClass()
print(id(a))
print(id(b))

데코레이터로 구현했을 때에는 다음과 같다. 코드 안에 주석으로 설명되어 있으므로 이해하는데 크게 힘들지는 않을 것이다.

 

 

4. enum initialization

java effective에 정의되어 있는 방법이다.

public enum SingleTon {
    INSTANCE;
    static String content = "";
    public static SingleTon getInstance(){
        content = "content";
        return INSTANCE;
    }
}

enum으로 했을 때의 장점

1. 더 간결하다

2. 추가 노력 없이 직렬화 할 수 있다.

3. 아주 복잡한 직렬화 상황이나 리플렉션 공격에서도 제 2의 인스턴스가 생기는 일을 완벽히 막아준다.

 

대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 밥업이다.

 

참고: https://blog.seotory.com/post/2016/03/java-singleton-pattern 

 

java singleton pattern (싱글톤 패턴)

 

blog.seotory.com

https://wikidocs.net/3693 

 

여러가지 싱글톤(singleton) 구현방법

[http://mataeoh.egloos.com/7081556](http://mataeoh.egloos.com/7081556) Method 1: Method type 여 ...

wikidocs.net

도서 참고: java effective, gof design pattern

반응형

'knowledge' 카테고리의 다른 글

[JAVA] [Python] 자바의 상속과 파이썬의 상속 비교  (0) 2021.11.19
[JAVA] Adapter 패턴에 대하여  (0) 2021.11.16
[Java] Prototype 생성 패턴  (0) 2021.11.02
[git] stash 활용  (0) 2021.10.28
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함