본문 바로가기
[CS] - Design Pattern

# 5. 싱글턴 패턴 응용

by Bebsae 2021. 8. 4.

이번 포스트는 파이썬으로 작성한점 양해 부탁드립니다.

아래의 포스트를 읽고 오는 것을 권장한다.
https://dev-ryuon.tistory.com/53

 

# 17. __new__() 와 __init__()

파이썬을 다루면서 클래스를 작성해보았다면 인스턴스를 생성해본 적이 있을 것이다. class o: pass o1 = o() 대체로 <"클래스명()' 연산을 통해 해당 클래스 타입의 인스턴스가 메모리에 할당된다..>

dev-ryuon.tistory.com


필자는 싱글턴 패턴을 사용하면서 한번 생성한 인스턴스를 재사용할 수 있다는 점(메모리 할당 면에서 효율적)과 전역적으로 해당 인스턴스를 사용할 수 있다는 점이 좋았다. 일단 파이썬으로 싱글턴 패턴을 어떻게 구현했는지 확인해보자.

class SingletonInstance: 
    __instance = None 
    
    @classmethod
    def __getInstance(cls): 
        return cls.__instance 
        
    @classmethod 
    def instance(cls, *args, **kwargs): 
        cls.__instance = cls(*args, **kwargs) 
        cls.instnace = cls.__getInstance 
        return cls.__instance


사용법은 간단하다. 위의 싱글턴 패턴을 사용하기 위해서는 싱글턴 패턴을 적용하려는 클래스에 상속을 해주어야 한다. 상속을 받은 클래스는 SingletonInstance 부모 클래스로부터 상속받은 정적 메소드인 instance() 를 호출하면 된다.

그러나 구글링을 해보아도 위 코드에 대한 상세한 설명이 없어서 필자가 해석한 나름대로 끄적여보려 한다.

일단 staticmethod와 classmethod의 차이를 알아야 한다. staticmethod는 자기 자신을 참조할 키워드가 없기 때문에 (self나 cls) 직접 클래스명을 명시하여 멤버를 참조해야 한다. 하지만, classmethod는 cls라는 자기 자신의 클래스를 참조하기 때문에 상속받은 자식 클래스의 멤버들을 참조할 수 있다. (중요한 점은 인스턴스 멤버변수는 참조할 수 없다. self.num 같은거..)

앞서 언급한 instance() 메소드를 호출하면 무슨 일이 일어나는지 한줄한줄 해석해보자.

1. cls(*args, **kwargs)를 호출하게 되면 상속받은 자식 클래스의 생성자를 호출하여 내부적으로 __new__()와 __init__()이 호출되어 초기화된 객체가 자식 클래스의 __instance 클래스 필드에 할당 된다.
2. 그 다음 현재 실행되고 있는 instance 메소드 포인터를 __getInstance 메소드 포인터를 가르키게 된다. 즉, 다음에 instance를 호출할 경우 instance가 아닌 __getInstance가 호출된다는 의미이다.
3. 그리고, 마지막으로 __instance 클래스 필드에 할당 되어 있는 인스턴스를 반환하게 된다.

그리고 다시 instance를 호출하게 된다면?
아까 instance 메소드 내부에서 instance에 대한 메소드 포인터를 __getInstance로 변경했으므로 더 이상 인스턴스를 새로 생성하지 않고 생성되어 있는 인스턴스를 반환하기만 한다.

그러나, 위와 같은 코드의 경우 객체를 없애고 다시 만들고 싶은 경우에는 딱히 좋은 방법이 떠오르지 않았다. 소멸자를 쓰는 것보다 일관적인 방법을 사용하고 싶었다. (메소드를 사용하여) 그래서 필자는 clearInstance와 removeInstance라는 메소드를 만들었다.

class SingletonInstance:
    __instance = None
    __tmp_method = None

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

    @classmethod
    def instance(cls, *args, **kwargs):
        """
        싱글톤 인스턴스는 특정 변수에 할당하지 않는 것이 좋다.
        ex)
        o1 = Obj.instance()
        o2 = Obj.clearInstance() 혹은 Obj.removeInstance()

        이후에도 o1 변수가 삭제를 시도한 인스턴스를 참조하고 있어 메모리에 남아있는 모습이 보인다.
        import sys
        sys.getrefcount(o1)  # 0이 아님 (0이 되어야 gc에 의에 메모리에서 해제된다.)
        """
        cls.__instance = cls(*args, **kwargs)
        cls.__tmp_method = cls.instance  # instance 메소드 포인터를 __tem_method 에 백업
        cls.instance = cls.__getInstance  # instance 메소드 포인터에 __getInstance 메소드 포인터로 대체 (instance 호출시 __getInstance 호출)
        return cls.__instance

    @classmethod
    def removeInstance(cls):
        """
        기존에 존재하던 인스턴스를 제거. instance()로 새로 호출해야함.
        :return:
        """
        if cls.__instance:
            cls.__instance = None  # 클래스 필드에 할당 되어 있는 인스턴스 제거
            cls.instance = cls.__tmp_method  # 백업해둔 메소드 포인터를 로드

    @classmethod
    def clearInstance(cls, *args, **kwargs):
        """
        기존에 존재하던 인스턴스를 제거하고 새로운 인스턴스를 반환한다.
        :param args:
        :param kwargs:
        :return:
        """
        if cls.__instance:
            obj = cls.__new__(cls)
            obj.__init__(*args, **kwargs)
            cls.__instance = obj
            cls.instance = cls.__getInstance
            return cls.__instance


clearInstance는 새로운 인스턴스를 만들어서 반환하는 것이고, removeInstance는 기존에 있던 인스턴스를 제거하기만 한다. 다시 instance()를 호출해야 새로운 인스턴스가 생성된다. (초기상태로 되돌리는 효과)

댓글