Переменная экземпляра класса 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 доступны в единственном числе для страницы просмотра только потому, что я сделал это переменной экземпляра в файле контроллера.