#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
, который сам по себе как объект является экземпляром classClass
:Speaker.class #=> Class
,Speaker.instance_variables #=> [:@message]