Ruby/Rails: Понимание методов и экземпляров Ruby getter-setter

Я новичок в рубине и программировании в целом и пытаюсь понять несколько ключевых концепций. Учитывая, что у меня есть класс Dog со следующими характеристиками.

class Dog
  attr_accessor :type, :popularity, :total

  def initialize(type = nil)
    @type = type
  end

  def total_dogs
    Dog.count
  end

  def total
    Dog.where(:type => self.type).size
  end

  def popularity
    total.to_f/total_dogs
  end
end

Я пытаюсь понять, как ruby ​​сохраняет атрибуты экземпляра с помощью методов получения/установки. Мне ясно, что если я создаю экземпляр нового экземпляра, а затем сохраняю атрибуты в этом экземпляре, эти атрибуты привязаны к этому экземпляру, потому что, если я смотрю на объект, атрибуты отображаются как таковые:

 @dog = Dog.new
 @dog
 => #<Dog:0x007fa8689ea238 @type=nil> 

Мне легко понять, что когда я передаю объект @dog, он всегда будет иметь атрибут @type равным нулю. Однако у меня возникают проблемы с пониманием ситуации, когда я передаю этот объект @dog другому классу. Как если бы я сделал:

 Owner.new(@dog)

Когда я нахожусь в классе владельцев и вызываю @dog.popularity, как он узнает значение популярности для этого экземпляра? Во время выполнения все методы обрабатываются, а затем этот экземпляр всегда привязан к значению в то время? Извините, если это не имеет смысла или я далеко.


person BC00    schedule 07.03.2013    source источник


Ответы (4)


Когда вы делаете

@dog = Dog.new

Вы делаете две важные вещи

1) Создайте переменную экземпляра @dog для любого объекта, внутри которого находится ваш код.

2) Создайте новый экземпляр Dog (со всеми его методами и атрибутами) и назначьте ссылку на него @dog

@dog - это переменная, которая просто указывает на экземпляр Dog ("экземпляр класса" обычно имеет то же значение, что и "объект"), который вы создали в этот момент. Вы можете установить другие переменные так, чтобы они указывали на один и тот же экземпляр, и в Ruby вы обычно передаете данные именно так. Объекты содержат переменные экземпляра, и эти переменные экземпляра указывают на другие объекты.

Используя оператор присваивания (например, "="), вы можете указать переменную на любой другой объект.

Чтобы ответить на ваши вопросы по очереди:

Когда я нахожусь в классе владельцев и вызываю @dog.popularity, как он узнает значение популярности для этого экземпляра?

Вы должны быть осторожны в Ruby (и в языках OO в целом), чтобы различать класс и объект в ваших описаниях и вопросах. Ruby Я предполагаю, что вы имеете в виду строку кода в классе Owner и намереваетесь работать с объектом-владельцем. Я бы также предположил, что @dog — это атрибут, который вы добавили к Owner.

В этом случае Ruby знает, потому что @dog указывает на объект Dog, который вы добавили в owner. Каждый объект Dog имеет собственную копию всех переменных экземпляра Dog. Однако в Ruby нужно соблюдать осторожность, потому что переменные указывают на объекты, которые вы не просто передаете в одном и том же объекте Dog всем владельцам (т. е. все они фактически используют одну и ту же собаку). Поэтому вам нужно понимать, когда вы создаете новые экземпляры (через new), а когда просто обрабатываете существующие ссылки.

Во время выполнения все методы обрабатываются, а затем этот экземпляр всегда привязан к значению в то время?

Нет. Во время выполнения базовый Ruby будет выполнять только те назначения, которые вы закодировали. Переменные экземпляра могут даже не существовать, пока не будет запущен код, который их присваивает. Если вы используете методы attr_reader и т. д., то переменные, по крайней мере, будут существовать (но будут нулевыми, если вы не назначите что-то во время инициализации)

person Neil Slater    schedule 07.03.2013
comment
Ах хорошо, спасибо очень полезно. Итак, в моем примере, когда создается экземпляр собаки, ей присваиваются значения в инициализации и во всех ее методах. Однако методы не оцениваются до тех пор, пока приложение/программа не попросит об этом что-то вроде @dog.popularity ... является ли значение, которое возвращает метод популярности, связанным с этим экземпляром собаки? например, если я вызову его снова, будет ли он снова обрабатывать метод/связанные методы или он просто узнает значение, когда оно было ранее вызвано? - person BC00; 08.03.2013
comment
Он обрабатывается каждый раз, он не помнит. Кстати, вы обычно не хотите или не нуждаетесь в обоих attr_accessor :foo и def foo - только одно или другое. Если все, что вам нужно, это читать и записывать переменные экземпляра, то attr_accessor является самым простым. Что-нибудь более сложное, вы хотите определить метод. - person Neil Slater; 08.03.2013

У Нила есть отличный ответ на этот вопрос, я просто хочу добавить к нему кое-что.

Считать собак :)

Для этого вам нужна переменная класса.

class Dog
  @@count = 0     # this is a class variable; all objects created by this class share it

  def initialize
    @@count += 1  # when we create a new Dog, we increment the count
  end
  def total
    @@count
  end
end

Есть еще один способ сделать это с «переменными экземпляра объекта класса», но это немного сложная тема.

Доступ к переменным экземпляра

В Ruby переменные — это просто ссылки на объекты/экземпляры.

 > x = 1
 => 1 
 > x.class
 => Fixnum
  > 1.instance_variables
 => [] 

x является ссылкой на объект '1', который является экземпляром класса Fixnum. Объект '1' является экземпляром Fixnum, который не содержит никаких переменных экземпляра. Это ничем не отличается от ссылки на новый экземпляр «Собака».

Точно так же вы можете сказать x = Dog.new , тогда x является ссылкой на экземпляр класса Dog.

class Dog
  attr_accessor :legs   # this defines the 'legs' and 'legs=' methods!
end

x = Dog.new
x.instance_variables
=> []     # if you would assign legs=4 during "initialize", then it would show up here
x.legs = 4      # this is really a method call(!) to the 'legs' method
x.instance_variables   # get created when they are first assigned a value
 => [:legs] 

Неважно, передаете ли вы такую ​​ссылку вызову метода, другому классу или просто оцениваете ее саму по себе — Ruby знает, что это ссылка на объект, и смотрит внутрь объекта и его цепочку наследования, чтобы решить проблемы.

Разрешение имен методов

Это была лишь частичная правда :) При интерпретации x.legs Ruby проверяет, есть ли в цепочке наследования классов объекта метод, который отвечает на это имя «ноги». Это не волшебный доступ к переменной экземпляра с тем же именем!

Мы можем определить метод «ноги», выполнив «attr_reader:legs» или «attr_accessor:legs», или сами определив метод.

class Dog
  def legs
     4     # most dogs have 4 legs, we don't need a variable for that
  end
end

x.legs     # this is a method call! it is not directly accessing a :legs instance variable!
 => 4
x.instance_variables
 => []     # there is no instance variable with name ":legs"

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

class Dog
   attr_accessor :legs  # this creates "def legs" and "def legs=" methods behind the scenes
   def legs        # here we explicitly override the "def legs" method from the line above.
      4
   end
end

x = Dog.new
x.legs       # that's the method call we implemented explicitly
 => 4
x.legs = 3   # we can still assign something to the instance_variable via legs=
 => 3
x.legs       # the last definition of a method overrides previous definitions
             # e.g. it overrides the automatically generated "legs" method
 => 4 

attr_accessor :legs — это просто краткая запись для этого:

class Dog
  def legs
    @legs
  end 
  def legs=(value)
    @legs = value
  end
end

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

Я надеюсь, что это имеет смысл для вас

person Tilo    schedule 07.03.2013

Когда вы создаете объект, вам не нужно использовать символ @. Переменная — это объект. Итак, если у вас несколько собак, вы должны сделать:

myDog = Dog.new(brown)
yourDog = Dog.new(white)

Оттуда вы можете сказать:

yourDog.type #white
myDog.type #brown

Что бы вы НЕ сделали, это:

@dog = Dog.new #myDog
@dog = Dog.new #yourDog

Если вам нужно несколько версий объекта, вы просто даете им разные имена. Поэтому, если вы создадите несколько собак и передадите их другим объектам, они будут работать. Например:

Скажем, ваш класс владельца:

Class Owner
def initialize(pet)
    puts "my pet is #{pet.type}"
end

Тогда использование переменной экземпляра будет:

me = Owner.new(myDog) #my pet is brown
you = Owner.new(yourDog) #my pet is white
person BlackHatSamurai    schedule 07.03.2013

И «тип», и «популярность» являются методами экземпляров «собаки». Их определение следующее:

class Dog
  # getter
  def type
    @type
  end

  def popularity
    total.to_f/total_dogs
  end
end

Это примерно эквивалентно:

class Dog
  attr_accessor :type

  def popularity
    total.to_f/total_dogs
  end
end

Обратите внимание, что attr_accessor — это просто ярлык для определения метода получения. Если вы определяете метод самостоятельно, использовать attr_accessor бессмысленно:

class Dog
  attr_accessor :popularity

  # this will override getter defined by attr_accessor
  def popularity
    total.to_f/total_dogs
  end
end

Вернемся к вашему вопросу: @dog.type вызывает метод типа для @dog, который возвращает свою переменную экземпляра; @dog.popularity вызывает метод популярности у @dog, который на лету выполняет вычисления (в соответствии с вашими указаниями) и возвращает результат. Здесь нет магии!

person Simon Perepelitsa    schedule 07.03.2013