Метод класса обращается к переменной экземпляра

#ruby

#ruby

Вопрос:

Я хотел бы понять этот код. Почему он возвращает Hello вместо Howdy! ?

 class Speaker
  @message = "Hello!"

  class << self
    @message = "Howdy!"

    def speak
      @message
    end
  end
end

puts Speaker.speak
  

Ответ №1:

Во-первых, ваше сообщение @message не является переменной экземпляра, или, скорее, не тем типом переменной экземпляра, о котором вы, возможно, подумали: это var экземпляра уровня класса, то есть переменная экземпляра Speaker самой по себе, которая как объект является экземпляром класса Class .

Вот версия кода, которая делает то, что вы пытаетесь сделать с локальной переменной и замыканием:

 class Speaker
  @message = "Hello!"

  class << self
    message = "Howdy!"
    define_method(:speak) { message }
  end
end

Speaker.speak
#=> "Howdy!"
  

И вот некоторый код, который иллюстрирует разницу между переменной экземпляра уровня класса и «обычной» переменной экземпляра:

 class Speaker
  @message = 'Howdy!'   # class-level instance variable

  def initialize
    @message = 'Hello!' # instance variable of Speaker's instances
  end

  def speak
    @message
  end

  class << self
    def speak
      @message
    end
  end
end

Speaker.speak
#=> "Howdy!"
Speaker.new.speak
#=> "Hello!"
  

Ответ №2:

Вот ваш код, за исключением того, что я определил метод класса обычным способом ( def self.speak... ). Поскольку метод класса — это не что иное, как метод экземпляра, определенный в классе singleton’ класса, это изменение является просто другим способом создания того же метода класса. (Если вы сомневаетесь в этом, запустите приведенный ниже код обоими способами.) Я внес это изменение, потому что думал, что это сделает мое объяснение происходящего более понятным. Я также добавил puts инструкцию.

 class Speaker
  @message = "Hello!"

  def self.speak
    puts "self=#{self}"
    @message
  end

  class << self
    @message = "Howdy!"
  end
end
  

Первая строка определения класса создает переменную экземпляра класса @message :

 Speaker.instance_variables
  #=> [:@message]
Speaker.instance_variable_get(:@message)
  #=> "Hello!"
  

С помощью constrast,

 @message = "Howdy!"
  

создает переменную экземпляра в одноэлементном классе Speaker :

 Speaker.singleton_class.instance_variables
  #=> [:@message]
Speaker.singleton_class.instance_variable_get(:@message)
  #=> "Howdy!"
  

Теперь вызываем speak на Speaker :

 Speaker.speak
  # self=Speaker
  #=> "Hello!" 
  

Поскольку self #=> Speaker , speak , очевидно, возвращает значение переменной экземпляра класса.

Для speak возврата значения переменной экземпляра, определенной в одноэлементном классе Speaker , мы можем написать следующее:

 class Speaker
  @message = "Hello!"

  def self.speak
    puts "self=#{self}"
    puts "singleton_class = #{singleton_class}"
    singleton_class.instance_variable_get :@message
  end

  class << self
    @message = "Howdy!"
  end
end

puts Speaker.speak
  # self=Speaker
  # singleton_class = #<Class:Speaker>
  # Howdy!
  

В последнем выражении, поскольку self равно Speaker и self является подразумеваемым получателем, когда нет явного получателя, "singleton_class эквивалентно Speaker.singleton_class .

Ответ №3:

Причина, по которой этот код возвращает ‘Hello’, заключается в том, что он пытается изменить переменную экземпляра в классе << selfблок.

Методы класса предназначены для всего, что не имеет отношения к отдельному экземпляру этого класса — переменные экземпляра привязаны к отдельным экземплярам класса, и мы не можем изменять переменные экземпляра на уровне класса.

Вместо использования переменной экземпляра в методе speak мы должны использовать переменную класса (обозначаемую @@).

В качестве примера следующий код вернет ‘Привет!’ —

 class Speaker
    @@message = "Hello!"

    class << self
        @@message = "Howdy!"

        def speak
           @@message
        end
    end
end

puts Speaker.speak
  

Комментарии:

1. @message выше приведена переменная экземпляра уровня класса, она принадлежит классу Speaker , который сам по себе как объект является экземпляром class Class : Speaker.class #=> Class , Speaker.instance_variables #=> [:@message]