Singleton 모듈

Singleton 모듈

Singleton 모듈은 싱글턴 패턴(Singleton pattern)을 구현해 주는 모듈이에요. "이 클래스의 인스턴스는 언제나 하나만 존재하게 하고 싶다"는 요구를 모듈 하나로 해결할 수 있죠.

출처: Ruby 4.0 API

본문

클래스에 include Singleton 한 줄만 넣으면 돼요.

class Klass
   include Singleton
   # ...
end

이렇게 하면 Klass의 인스턴스는 하나만 만들 수 있어요.

a,b = Klass.instance, Klass.instance

a == b
# => true

Klass.new
# => NoMethodError - new is private ...

Klass.instance를 몇 번을 불러도 항상 같은 객체가 돌아오고, new는 private이라 직접 호출할 수 없어요.

인스턴스는 Klass.instance()를 처음 호출하는 시점에 만들어져요.

class OtherKlass
  include Singleton
  # ...
end

ObjectSpace.each_object(OtherKlass){}
# => 0

OtherKlass.instance
ObjectSpace.each_object(OtherKlass){}
# => 1

처음에는 인스턴스가 0개였다가, instance를 호출한 뒤에는 정확히 1개가 되는 걸 볼 수 있어요.

이런 동작은 상속과 복제(clone)에서도 유지돼요.

구현 방식 (Implementation)

위 동작은 이렇게 이뤄져요:

  • Klass.newKlass.allocate를 private으로 만든다.
  • Klass.inherited(sub_klass)Klass.clone()을 오버라이드해서, 상속·복제돼도 싱글턴 성질이 유지되게 한다.
  • 호출할 때마다 같은 객체를 반환하는 Klass.instance() 메서드를 제공한다.
  • Klass._load(str)을 오버라이드해서 Klass.instance()를 호출하게 한다.
  • Klass#cloneKlass#dup을 오버라이드해서 TypeError를 던지게 해 복제를 막는다.

Singleton과 Marshal

기본적으로 Singleton_dump(depth)는 빈 문자열을 반환해요. 그러면 마샬링(Marshal)이 기본적으로 상태 정보(가령 인스턴스 변수)를 떼어버려요. 싱글턴을 쓰는 클래스는 커스텀 _load(str)_dump(depth)를 제공하면 인스턴스의 이전 상태 일부를 보존할 수 있어요.

require 'singleton'

class Example
  include Singleton
  attr_accessor :keep, :strip
  def _dump(depth)
    # this strips the @strip information from the instance
    Marshal.dump(@keep, depth)
  end

  def self._load(str)
    instance.keep = Marshal.load(str)
    instance
  end
end

a = Example.instance
a.keep = "keep this"
a.strip = "get rid of this"

stored_state = Marshal.dump(a)

a.keep = nil
a.strip = nil
b = Marshal.load(stored_state)
p a == b  #  => true
p a.keep  #  => "keep this"
p a.strip #  => nil

_dump에서 @keep만 저장하고, _load에서 그 값을 복원해서 같은 싱글턴 인스턴스에 다시 넣어 주는 예시예요.

Constants

_load(str)

기본적으로 instance()를 호출해요. 싱글턴 상태를 보존하려면 오버라이드하면 돼요.

instance

싱글턴 인스턴스를 반환해요.