Singleton to kreacyjny wzorzec projektowy gwarantujący istnienie tylko jednego obiektu danego rodzaju. Udostępnia też pojedynczy punkt dostępowy do takiego obiektu z dowolnego miejsca w programie.
Singleton charakteryzuje się prawie takimi samymi zaletami i wadami jak zmienne globalne i chociaż jest bardzo poręczny, to jednak psuje modularność kodu.
Nie można przenieść klasy zależnej od Singletona i użyć jej w innym kontekście bez równoczesnego przeniesienia tego drugiego. To ograniczenie zazwyczaj ujawnia się na etapie tworzenia testów jednostkowych.
Przykłady użycia: Wielu twórców oprogramowania uważa Singleton za antywzorzec, przez to jego wykorzystanie w kodzie Ruby maleje.
Identyfikacja: Singleton można rozpoznać po statycznej metodzie kreacyjnej zwracającej jakiś obiekt którego instancja jest przechowywana w pamięci podręcznej.
Implementacja naiwna
Łatwo jest zaimplementować wzorzec Singleton niechlujnie — wystarczy ukryć konstruktor i zaimplementować statyczną metodę kreacyjną.
Ta sama klasa będzie działać nieprawidłowo w środowisku wielowątkowym — różne wątki mogą wywołać metodę kreacyjną w tym samym momencie, otrzymując wiele instancji klasy Singleton.
main.rb: Przykład koncepcyjny
# The Singleton class defines the `instance` method that lets clients access the # unique singleton instance. class Singleton @instance = new private_class_method :new # The static method that controls the access to the singleton instance. # # This implementation let you subclass the Singleton class while keeping just # one instance of each subclass around. def self.instance @instance end # Finally, any singleton should define some business logic, which can be # executed on its instance. def some_business_logic # ... end end # The client code. s1 = Singleton.instance s2 = Singleton.instance if s1.equal?(s2) print 'Singleton works, both variables contain the same instance.' else print 'Singleton failed, variables contain different instances.' end
output.txt: Wynik działania
Singleton works, both variables contain the same instance.
Singleton z bezpieczeństwem wątków
Aby pozbyć się wyżej wymienionego problemu, trzeba zsynchronizować wątki w momencie pierwszego tworzenia obiektu Singleton.
main.rb: Przykład koncepcyjny
# The Singleton class defines the `instance` method that lets clients access the # unique singleton instance. class Singleton attr_reader :value @instance_mutex = Mutex.new private_class_method :new def initialize(value) @value = value end # The static method that controls the access to the singleton instance. # # This implementation let you subclass the Singleton class while keeping just # one instance of each subclass around. def self.instance(value) return @instance if @instance @instance_mutex.synchronize do @instance ||= new(value) end @instance end # Finally, any singleton should define some business logic, which can be # executed on its instance. def some_business_logic # ... end end # @param [String] value def test_singleton(value) singleton = Singleton.instance(value) puts singleton.value end # The client code. puts "If you see the same value, then singleton was reused (yay!)\n"\ "If you see different values, then 2 singletons were created (booo!!)\n\n"\ "RESULT:\n\n" process1 = Thread.new { test_singleton('FOO') } process2 = Thread.new { test_singleton('BAR') } process1.join process2.join
output.txt: Wynik działania
If you see the same value, then singleton was reused (yay!) If you see different values, then 2 singletons were created (booo!!) RESULT: FOO FOO