Что такое синглтон-класс в Ruby? - PullRequest
0 голосов
/ 22 апреля 2020

У меня проблемы с пониманием концепции собственного класса или синглтон-класса в ruby. Я много читал, что собственный класс является классом класса . Это не дает мне никакого смысла, так как для меня класс класса на самом деле Class, поскольку все классы на самом деле являются экземплярами класса Class.

Еще одна вещь, которую я не совсем понимаю, это следующее утверждение: метод класса на самом деле метод экземпляра класса собственного класса. Собственный класс доступен следующим образом:

YourClass = Class.new
class << YourClass
  def class_method
  end
end

Но если собственный класс действительно является классом YourClass (то есть Class), не следует ли предыдущему коду кода открыть класс Class и добавить к нему метод экземпляра class_method, делающий его доступным для всех его будущих экземпляров (т. е. любого обычного класса, определенного в будущем)?

На самом деле я чувствую, что класс singleton не совпадает с Class. Когда вы делаете:

class MyClass
end

MyClass.singleton_class

, вы получаете #<Class:MyClass>, который отличается от вывода MyClass.class => Class

Что это за вывод #<Class:MyClass>? Это не имеет ничего общего с пространством имен, так как в противном случае их будет два: Class::MyClass ...

Я ищу простое и однозначное объяснение концепции собственного класса, чтобы прояснить мои идеи.

Ответы [ 3 ]

2 голосов
/ 23 апреля 2020

Одиночные классы содержат методы, которые 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 классы тоже являются объектами, и все объекты работают одинаково.

1 голос
/ 23 апреля 2020

Я думаю, что вы немного сбились с пути, путая идею собственного класса с идеей экземпляра класса Ruby. Они не совсем одинаковы; правильнее будет сказать, что Ruby реализует определения классов внутри себя как два объекта.

Это достаточно легко продемонстрировать:

$ irb
> ObjectSpace.count_objects[:T_CLASS]
 => 1285 
> Foo = Class.new { def instance_method; end }
 => Foo 
> ObjectSpace.count_objects[:T_CLASS]
 => 1287

Внутри каждого объекта есть указатель klass, который на поверхности указывает на класс, экземпляром которого является объект. Однако каждый объект (кроме некоторых примитивов, таких как Integer, Float и Symbol) также имеет экземпляр собственного класса, и именно на это указывает указатель klass. В случае классов указатель klass фактически указывает не на класс Class, а на одноэлементный объект, который является частью определения класса и содержит таблицу методов, которая содержит методы класса.

Как объясняет ссылка, которую предоставляет другой mh, таблица методов «обычного» объекта класса содержит все методы экземпляра класса, в то время как таблица методов объекта eigenclass содержит все методы класса класса. Это механизм, который предотвращает доступ всех методов класса к любому экземпляру класса Class.

Итак, собственный класс ( eigen в переводе с немецкого означает «собственный» в смысле «мой») - это класс этого класса, поэтому он также называется метаклассом . (Посмотрите на исходный код Class::new, и вы увидите вызов rb_make_metaclass.)

Именно поэтому вызов MyClass.singleton_class возвращает #Class:MyClass, а не Class как Звоните MyClass.class делает. Этот синтаксис аналогичен p Foo.new, возвращающему что-то вроде #<Foo:0x00007f9c0d190400>, который является классом и указателем на экземпляр. С #Class:MyClass, MyClass - указатель на экземпляр. Итак, это описывает ссылку на метакласс MyClass, или класс MyClass. Это не следует путать с тем, к какому классу MyClass относится экземпляр, который, конечно, равен Class.

Если вам интересно, указатель klass в экземпляре eigenclass на самом деле указывает на себя. Это подтверждается тем фактом, что MyClass.singleton_class.singleton_class возвращает #Class:#Class:MyClass.

Для более полного обзора см. Демистификация Ruby Singleton Classes . Чтобы узнать, что происходит в исходном коде, см. Ruby Руководство по взлому: глава 4 . Наконец, Ruby Под микроскопом является отличным ресурсом для всестороннего глубокого погружения во внутренние органы Ruby.

[Отредактировано для включения некоторых обсуждений в комментарии]

0 голосов
/ 23 апреля 2020

Eigenclass больше не является именем, используемым в мире Ruby, потому что Ruby официально представил метод Object#singleton_class в том, что я не знаю, какая версия (извините).

Каждый объект в Ruby, будучи «нормальным» объектом или классом или даже одноэлементным классом, имеет свой собственный одноэлементный класс.

Одноэлементный класс - это класс.

Object.new.singleton_class.is_a?(Class)  #=> true

Одиночный класс имеет и имеет только один экземпляр, и это объект, для которого вы вызываете singleton_class. Например, единственный экземпляр foo.singleton_class - это foo.

Ruby позволяет добавлять методы к отдельным объектам.

a = Object.new
b = Object.new

def a.foo
  'foo'
end

a.foo  #=> "foo"
b.foo  #=> raises NoMethodError 

Но все методы экземпляра должны быть определены в классе Так в каком классе определяется a.foo? Ответ a.singleton_class. Поскольку a.singleton_class имеет только один экземпляр a, метод экземпляра foo можно вызывать только для a, но не для b, хотя они относятся к одному типу.

Как для одноэлементных классов класса их цель состоит в том, чтобы хранить методы класса «нормальных классов» или, если вы немного искривляете мозг, методы экземпляров, которые связаны с отдельными экземплярами классов.

Не Вы чувствуете, что объектная модель Ruby последовательна и красива?

...