Одиночные классы содержат методы, которые c указывают на один объект.
Для универсальных c объектов это удобная функция. Но для занятий это важно. Давайте начнем с объектов:
Синглтон-классы для объектов
Методы экземпляра обычно определяются в классах. Все экземпляры одного класса используют одни и те же методы экземпляров. Класс синглтона находится между объектом и его классом. Он позволяет каждому экземпляру иметь свой собственный набор методов, независимый от других экземпляров.
Если у нас есть два класса, Foo
и Bar
с 2 экземплярами каждый a
, b
и c
, d
:
class Foo ; end
class Bar ; end
a = Foo.new #=> #<Foo:0x00007fc280963008>
b = Foo.new #=> #<Foo:0x00007f8319016b18>
c = Bar.new #=> #<Bar:0x00007fa66c8d7290>
d = Bar.new #=> #<Bar:0x00007f94d5106ac8>
Вы бы имели такую структуру классов: (упрощенно, исключая модули)
object singleton class class superclass ...
a ── #<Class:#<Foo:0x00007fc280963008>> ─┐
├─ Foo ─┐
b ── #<Class:#<Foo:0x00007f8319016b18>> ─┘ │
├─ Object ── BasicObject
c ── #<Class:#<Bar:0x00007fa66c8d7290>> ─┐ │
├─ Bar ─┘
d ── #<Class:#<Bar:0x00007f94d5106ac8>> ─┘
Ruby создает эти одиночные классы лениво, например, когда вызов singleton_class
.
Поэтому при определении метода a.hello
он сохраняется не в классе a
Foo
, а в классе a
singleton:
def a.hello
'hello from a'
end
a.method(:hello).owner
#=> #<Class:#<Foo:0x00007fc280963008>> <-- a's singleton class
Из-за этого b
не видит этот метод, хотя оба являются Foo
экземплярами:
b.hello #=> NoMethodError: undefined method `hello'
И мы даже можем определить метод с тем же именем для b
без вмешательства в a
:
def b.hello
'hello from b'
end
b.method(:hello).owner
#=> #<Class:#<Foo:0x00007f8319016b18>> <-- b's singleton class
a.hello #=> "hello from a"
b.hello #=> "hello from b"
Мы также можем определить шаблон c hello
в Foo
и переопределить его на уровне экземпляра: (обычно вы этого не делаете , но это возможно)
class Foo
def hello
'hello'
end
end
def a.hello
"#{super} from a"
end
def b.hello
"b says #{super.upcase}!"
end
a.hello #=> "hello from a"
b.hello #=> "b says HELLO!"
c = Foo.new
c.hello #=> "hello"
Singleton классы для классов
Вышесказанное особенно важно для классов. Каждый класс является экземпляром Class
:
Foo.class #=> Class
Допустим, мы хотели иметь метод Foo.hello
, где бы мы его определили?
Методы экземпляра обычно определяются в класс экземпляра, поэтому мы могли бы определить его в классе Foo
:
class Class
def hello
'Hello from Foo'
end
end
Foo.hello
#=> "Hello from Foo"
Но это сделало бы метод доступным для всех экземпляров Class
:
Bar.hello
#=> "Hello from Foo"
String.hello
#=> "Hello from Foo"
Было бы лучше иметь место, эксклюзивное для экземпляра Foo
. И это место - Foo
синглтон-класс:
def Foo.hello
'Hello from Foo'
end
или
class Foo
def self.hello # <-- self is Foo, so this is just "def Foo.hello"
'hello from Foo'
end
end
Как и a.hello
выше, этот метод доступен только для Foo
:
Foo.hello #=> "hello from Foo"
Bar.hello #=> NoMethodError
Мы называем эти методы методами класса , но на самом деле они являются просто методами экземпляра синглтон-класса:
Foo.method(:hello).owner
#=> #<Class:Foo> <-- Foo's singleton class
Foo.method(:hello).unbind == Foo.singleton_class.instance_method(:hello)
#=> true
И если сравнить методы синглтона для классов с теми для объектов, вы увидите, что они идентичны. Это потому, что в Ruby классы тоже являются объектами, и все объекты работают одинаково.