Чому власний клас не еквівалентний self.class, коли він виглядає настільки подібним?


83

Я десь пропустив пам’ятку, і сподіваюся, ти поясниш мені це.

Чому власний клас об'єкта відрізняється від self.class?

class Foo
  def initialize(symbol)
    eigenclass = class << self
      self
    end
    eigenclass.class_eval do
      attr_accessor symbol
    end
  end
end

Мій шлейф логіки, який прирівнює власний клас class.selfдоволі простий:

class << selfє способом оголошення методів класу, а не методів екземпляра. Це ярлик для def Foo.bar.

Отже, у межах посилання на об’єкт класу повернення selfмає бути ідентичним self.class. Це відбувається тому , class << selfщо набір selfдля Foo.classдля визначення методів класу / атрибутів.

Я просто розгубився? Або це підлий фокус метапрограмування Ruby?

Відповіді:


122

class << self- це більше, ніж просто спосіб оголошення методів класу (хоча його можна використовувати і таким чином). Можливо, ви бачили таке вживання, як:

class Foo
  class << self
    def a
      print "I could also have been defined as def Foo.a."
    end
  end
end

Це працює, і це еквівалентно def Foo.a, але те, як це працює, є делікатним. Секрет полягає в тому self, що в цьому контексті посилається на об'єкт Foo, клас якого є унікальним, анонімним підкласом Class. Цей підклас називається Foo«s eigenclass . Таким чином , def aстворюється новий метод , званий aв Fooeigenclass «S, доступний за нормальним синтаксису виклику методу: Foo.a.

А тепер давайте розглянемо інший приклад:

str = "abc"
other_str = "def"

class << str
  def frob
    return self + "d"
  end
end

print str.frob # => "abcd"
print other_str.frob # => raises an exception, 'frob' is not defined on other_str

Цей приклад такий самий, як і останній, хоча спочатку це важко сказати. frobвизначається не для Stringкласу, а для власного класу strунікального анонімного підкласу String. Так само strмає frobметод, але екземпляри Stringзагалом - ні. Ми могли б також замінити методи String (дуже корисні в певних хитрих сценаріях тестування).

Тепер ми готові зрозуміти ваш оригінальний приклад. Усередині Fooініціалізації метод «и, selfвідноситься не до класу Foo, але в якийсь - то конкретної інстанції в Foo. Його власний клас є підкласом Foo, але це не так Foo; цього не могло бути, інакше фокус, який ми побачили у другому прикладі, не міг спрацювати. Отже, щоб продовжити приклад:

f1 = Foo.new(:weasels)
f2 = Foo.new(:monkeys)

f1.weasels = 4 # Fine
f2.monkeys = 5 # Also ok
print(f1.monkeys) # Doesn't work, f1 doesn't have a 'monkeys' method.

Сподіваюся, це допомагає.


Тоді кожен екземпляр є анонімним підкласом створеного класу?
Роберт К.

21
Клас кожного екземпляра - це анонімний підклас створеного класу. клас f1 - це анонімний підклас Foo, клас Foo - анонімний підклас класу.
Девід Сейлер

6
приємна відповідь :) багато людей не розуміють цього так чітко, як ви.
horseyguy

3
Чим власний клас f1 відрізняється тоді, концептуально, від фактичного екземпляра f1. Якщо f1 - це єдиний приклад, який коли-небудь матиме доступ до методів свого власного класу, чи не порушується різниця між f1 та його власним класом?
elju

1
@elju Так, певно. Дійсно важлива різниця між "Foo" та "власним класом f1"; якщо у вас це є, ви, мабуть, добре.
Девід Сейлер

46

Найпростіша відповідь: власний клас неможливо створити.

class F
 def eigen
  class << self 
   self
  end
 end
end
F.new.eigen.new #=> TypeError: can't create instance of virtual class

у вас може бути лише 1 бал на цьому веб-сайті, але ви мені подобаєтесь і ваш стиль.
horseyguy

Погодьтеся з перилами; це чудова відповідь
Крістофер Скотт

3
Це надзвичайно проникливий та корисний коментар, який IFF вже прочитав у відповіді @ DavidSeiler вище.
Джаз

Тео Пауер демонструє виняток, який викликано.
Нова Олександрія

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.