Переменная экземпляра класса Ruby против переменной класса

Я прочитал " Когда устанавливаются переменные экземпляра Ruby? ", Но я не могу понять, когда использовать переменные экземпляра класса.

Переменные класса являются общими для всех объектов класса, переменные экземпляра принадлежат одному объекту. Там не так много места, чтобы использовать переменные экземпляра класса, если у нас есть переменные класса.

Может ли кто-нибудь объяснить разницу между этими двумя и когда их использовать?

Вот пример кода:

class S
  @@k = 23
  @s = 15
  def self.s
    @s
  end
  def self.k
     @@k
  end

end
p S.s #15
p S.k #23

Теперь я понимаю, что переменные экземпляра класса не передаются по цепочке наследования!

9 ответов

Решение

Переменная экземпляра в классе:

class Parent
  @things = []
  def self.things
    @things
  end
  def things
    self.class.things
  end
end

class Child < Parent
  @things = []
end

Parent.things << :car
Child.things  << :doll
mom = Parent.new
dad = Parent.new

p Parent.things #=> [:car]
p Child.things  #=> [:doll]
p mom.things    #=> [:car]
p dad.things    #=> [:car]

Переменная класса:

class Parent
  @@things = []
  def self.things
    @@things
  end
  def things
    @@things
  end
end

class Child < Parent
end

Parent.things << :car
Child.things  << :doll

p Parent.things #=> [:car,:doll]
p Child.things  #=> [:car,:doll]

mom = Parent.new
dad = Parent.new
son1 = Child.new
son2 = Child.new
daughter = Child.new

[ mom, dad, son1, son2, daughter ].each{ |person| p person.things }
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]

С помощью переменной экземпляра в классе (не в экземпляре этого класса) вы можете хранить что-то общее с этим классом, не имея подклассов, автоматически получающих их (и наоборот). С переменными класса вы можете не писать self.class из объекта экземпляра, и (когда это желательно) вы также получаете автоматический общий доступ по всей иерархии классов.


Объединение их вместе в один пример, который также охватывает переменные экземпляра на экземплярах:

class Parent
  @@family_things = []    # Shared between class and subclasses
  @shared_things  = []    # Specific to this class

  def self.family_things
    @@family_things
  end
  def self.shared_things
    @shared_things
  end

  attr_accessor :my_things
  def initialize
    @my_things = []       # Just for me
  end
  def family_things
    self.class.family_things
  end
  def shared_things
    self.class.shared_things
  end
end

class Child < Parent
  @shared_things = []
end

И тогда в действии:

mama = Parent.new
papa = Parent.new
joey = Child.new
suzy = Child.new

Parent.family_things << :house
papa.family_things   << :vacuum
mama.shared_things   << :car
papa.shared_things   << :blender
papa.my_things       << :quadcopter
joey.my_things       << :bike
suzy.my_things       << :doll
joey.shared_things   << :puzzle
suzy.shared_things   << :blocks

p Parent.family_things #=> [:house, :vacuum]
p Child.family_things  #=> [:house, :vacuum]
p papa.family_things   #=> [:house, :vacuum]
p mama.family_things   #=> [:house, :vacuum]
p joey.family_things   #=> [:house, :vacuum]
p suzy.family_things   #=> [:house, :vacuum]

p Parent.shared_things #=> [:car, :blender]
p papa.shared_things   #=> [:car, :blender]
p mama.shared_things   #=> [:car, :blender]
p Child.shared_things  #=> [:puzzle, :blocks]  
p joey.shared_things   #=> [:puzzle, :blocks]
p suzy.shared_things   #=> [:puzzle, :blocks]

p papa.my_things       #=> [:quadcopter]
p mama.my_things       #=> []
p joey.my_things       #=> [:bike]
p suzy.my_things       #=> [:doll] 

# переменная экземпляра класса доступна только для метода класса, но не для методов экземпляра, тогда как переменная класса доступна как для методов экземпляра, так и для методов класса. Также переменные экземпляра класса теряются в цепочке наследования, тогда как переменные класса - нет.

class Vars

  @class_ins_var = "class instance variable value"  #class instance variable
  @@class_var = "class variable value" #class  variable

  def self.class_method
    puts @class_ins_var
    puts @@class_var
  end

  def instance_method
    puts @class_ins_var
    puts @@class_var
  end
end

Vars.class_method

puts "see the difference"

obj = Vars.new

obj.instance_method

class VarsChild < Vars


end

VarsChild.class_method

Я считаю, что главное (только?) Другое - это наследование

class T < S
end

p T.k
=> 23

S.k = 24
p T.k
=> 24

p T.s
=> nil

Переменные класса являются общими для всех "экземпляров класса" (т. Е. Подклассов), тогда как переменные экземпляра класса специфичны только для этого класса. Но если вы никогда не собираетесь расширять свой класс, разница чисто академическая.

Как говорили другие, переменные класса являются общими для данного класса и его подклассов. Переменные экземпляра класса принадлежат ровно одному классу; его подклассы являются отдельными.

Почему такое поведение существует? Ну, все в Ruby - это объект, даже классы. Это означает, что каждый класс имеет объект класса Class (или, скорее, подкласс Class) соответствует этому. (Когда ты сказал class Fooвы действительно объявляете константу Foo и присвоение ему объекта класса.) И каждый объект Ruby может иметь переменные экземпляра, поэтому объекты класса также могут иметь переменные экземпляра.

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

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

Официальный Ruby FAQ: В чем разница между переменными класса и переменными экземпляра класса?

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

Переменные класса в некотором роде можно рассматривать как глобальные переменные в контексте иерархии наследования со всеми проблемами, связанными с глобальными переменными. Например, переменная класса может (случайно) быть переназначена любым из ее подклассов, затрагивая все другие классы:

class Woof

  @@sound = "woof"

  def self.sound
    @@sound
  end
end

Woof.sound  # => "woof"

class LoudWoof < Woof
  @@sound = "WOOF"
end

LoudWoof.sound  # => "WOOF"
Woof.sound      # => "WOOF" (!)

Или, класс предка мог позже быть открыт и изменен с возможно удивительными эффектами:

class Foo

  @@var = "foo"

  def self.var
    @@var
  end
end

Foo.var  # => "foo" (as expected)

class Object
  @@var = "object"
end

Foo.var  # => "object" (!)

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

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

class C
  @@c = 'c'
  def self.c_val
    @@c
  end
end

C.c_val
 => "c" 

class D < C
end

D.instance_eval do 
  def change_c_val
    @@c = 'd'
  end
end
 => :change_c_val 

D.change_c_val
(irb):12: warning: class variable access from toplevel
 => "d" 

C.c_val
 => "d" 

Rails представляет удобный метод с именем class_attribute. Как следует из названия, оно объявляет атрибут уровня класса, значение которого наследуется подклассами. Доступ к значению class_attribute возможен как в методе singleton, так и в экземпляре, как в случае с переменной экземпляра класса. Однако огромное преимущество class_attribute в Rails состоит в том, что подклассы могут изменять свое собственное значение, и это не повлияет на родительский класс.

class C
  class_attribute :c
  self.c = 'c'
end

 C.c
 => "c" 

class D < C
end

D.c = 'd'
 => "d" 

 C.c
 => "c" 

Простой пример, показывающий

  • наследуемости от переменных класса
  • инкапсуляция из экземпляра класса переменных

ПРИМЕЧАНИЕ. Это удобство использования вместо того, чтобы добавлять все методы внутри этого блока с помощью self. Обратите внимание class << self изменяет selfпоэтому он указывает на метакласс для Parent(см. /questions/5489988/klass-sobstvennaya-idioma-v-ruby/5490010#5490010)

Пример кода

      class Parent
  class << self
    attr_reader :class_instance_var

    def class_instance_var=(value)
      @class_instance_var="set by #{self.name} to #{value}"
    end

    def class_var
      @@class_var
    end

    def class_var=(value)
      @@class_var = "set by #{self.name} to #{value}"
    end
  end
end

class Child < Parent
end

# use the instance separately in parent and subclass
puts "\n* Exercising class instance variable setters
* Setting Parent and Child class instance variables differently
* Parent.class_instance_var = 1000\n* Child.class_instance_var = 2000\n\n"

Parent.class_instance_var = 1000
Child.class_instance_var = 2000
puts "Parent.class_instance_var=(#{Parent.class_instance_var})"
puts "Child.class_instance_var=(#{Child.class_instance_var})"

# set class variable in via parent (changes both in parent and subclass)
puts "\n* Exercising Parent class variable setter
* Set class variable value to 3000 using parent, it changes in Child also
* Parent.class_var = 3000\n\n"

Parent.class_var = 3000
puts "Parent.class_var=(#{Parent.class_var})"
puts "Child.class_var=(#{Child.class_var})"

# set class variable in via subclass (changes both in parent and subclass)
puts "\n* Exercising Child class variable setter
* Set class variable value to 5000 using child, it changes in Parent also
* Child.class_var = 5000\n\n"

Child.class_var = 5000
puts "Parent.class_var=(#{Parent.class_var})"
puts "Child.class_var=(#{Child.class_var})"

Вывод с использованием ruby ​​v3.0.2

      * Exercising class instance variable setters
* Setting Parent and Child class instance variables differently
* Parent.class_instance_var = 1000
* Child.class_instance_var = 2000

Parent.class_instance_var=(set by Parent to 1000)
Child.class_instance_var=(set by Child to 2000)

* Exercising Parent class variable setter
* Set class variable value to 3000 using parent, it changes in Child also
* Parent.class_var = 3000

Parent.class_var=(set by Parent to 3000)
Child.class_var=(set by Parent to 3000)

* Exercising Child class variable setter
* Set class variable value to 5000 using child, it changes in Parent also
* Child.class_var = 5000

Parent.class_var=(set by Child to 5000)
Child.class_var=(set by Child to 5000)

Для тех, кто имеет опыт работы на C++, вас может заинтересовать сравнение с эквивалентом C++:

class S
{
private: // this is not quite true, in Ruby you can still access these
  static int    k = 23;
  int           s = 15;

public:
  int get_s() { return s; }
  static int get_k() { return k; }

};

std::cerr << S::k() << "\n";

S instance;
std::cerr << instance.s() << "\n";
std::cerr << instance.k() << "\n";

Как мы можем видеть, k это staticкак переменная. Это на 100% похоже на глобальную переменную, за исключением того, что она принадлежит классу (с определеннойобластью видимости). Это позволяет избежать конфликтов между одноименными переменными. Как и любая глобальная переменная, существует только один экземпляр этой переменной, и изменение его всегда видно всем.

С другой стороны, s- значение, зависящее от объекта. У каждого объекта есть собственный экземпляр значения. В C++ вы должны создать экземпляр, чтобы иметь доступ к этой переменной. В Ruby определение класса само по себе является экземпляром класса (в JavaScript это называется прототипом), поэтому вы можете получить доступsиз класса без дополнительной инстанциации. Экземпляр класса может быть изменен, но модификацияs будет специфичным для каждого экземпляра (каждый объект типа S). Таким образом, изменение одного значения не изменит значения другого.

Итак, как вы знаете, переменные класса - это переменные, которые доступны для определенного класса, и синтаксис выглядит так:

class myClass
   @@teams = ["A's","Tigers"]
end

Тем не менее, вы редко будете использовать переменные класса в реальных приложениях, потому что вы можете сделать то же самое с помощью локальных переменных или переменных экземпляра. Нет ничего плохого, если вы используете переменные класса, но это обычно не используется большинством разработчиков. На самом деле, локальные переменные и переменные экземпляра, вероятно, составляют более 98 процентов переменных в вашем приложении, поэтому стоит ознакомиться с ними.

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

class PortfolioController < ApplicationController
  before_action :set_portfolio_item, only: [:edit, :update, :show, :destroy]
  layout 'portfolio'
  access all: [:show, :index, :angular], user: {except: [:destroy, :new, :create]}

  def index
    # this calls the model
    @portfolio_items = Portfolio.by_position
  end
end

В этом коде вы можете видеть, что есть переменная экземпляра с именем @portfolio_items. Эта переменная создается в индексе метода и недоступна другим методам в файле. Теперь, почему я не просто сделал это локальной переменной, поскольку она не доступна другим методам в классе?

Причина этого в том, что Rails структурирован таким образом, что файлы представлений и контроллеров подключены для связи друг с другом, поэтому к этой переменной экземпляра @portfolio_items можно получить доступ в связанном файле представлений, например так:

<%= form_for(@portfolio_item) do |f| %>
 <% if @portfolio_item.errors.any? %>
  <% @portfolio_item.errors.full_messages.each do |error| %>
    <% alert_generator error %>
  <% end %>
 <% end %>

Теперь @portfolio_items доступны в единственном числе для страницы просмотра только потому, что я сделал это переменной экземпляра в файле контроллера.

Другие вопросы по тегам