Должен ли я использовать переменные класса или переменные экземпляра класса для статических переменных класса в Ruby?

class Something
    @@variable = 'Class variable'

    def give_me
        @@variable
    end
end

class OtherThing
    @variable = 'Instance variable with an interface'

    class << self
        attr_accessor :variable
    end

    def give_me
        self.class.variable
    end
end

p Something.new.give_me
p OtherThing.new.give_me

Я хочу знать, какой из них я должен использовать? Каковы преимущества и недостатки каждого?

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

  1. Частный, если вы не сделаете интерфейс
  2. Разделено между наследствами
  3. короче писать

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

  1. Публичные, поскольку для доступа к ним необходимо использовать интерфейс.
  2. Не распределяется между наследниками, но устанавливается равным нулю при наследовании
  3. Дольше писать

Что еще я должен иметь в виду?


person Zequez    schedule 16.08.2011    source источник
comment
Должен ли я использовать переменные класса или переменные класса-экземпляра...? - Ничего из вышеперечисленного, если можно помочь. Обычно никогда не бывает чего-то одного.   -  person Andrew Grimm    schedule 16.08.2011
comment
Переменные экземпляра класса являются частными. Публичные переменные экземпляра не существуют в Ruby. Если вы хотите, чтобы метод доступа был закрытым, просто определите его как закрытый метод.   -  person Marnen Laibow-Koser    schedule 16.08.2011
comment
Эндрю: Неправильно. Достаточно часто классу требуется хранить состояние, которое не привязано к какому-либо конкретному экземпляру.   -  person Marnen Laibow-Koser    schedule 16.08.2011
comment
@marnen: Можете ли вы привести пример?   -  person Andrew Grimm    schedule 17.08.2011
comment
Эндрю: Бывают случаи, когда класс хочет знать, сколько экземпляров существует (хорошо, вы можете посмотреть в ObjectSpace, но что, если вы хотите запомнить это?) или где класс должен хранить настройки конструктора по умолчанию для вновь созданных экземпляров. Я мог бы продолжать; дело в том, что иногда класс имеет свои свойства, как и любой другой объект.   -  person Marnen Laibow-Koser    schedule 18.08.2011


Ответы (4)


Недавно я обнаружил, что ActiveSupport определяет class_inheritable_accessor, который делает то же, что и переменные класса-экземпляра, с тем преимуществом, что объекты не являются общими для наследования, и вы можете иметь значение по умолчанию для переменной при создании подклассов.

class Foo
  class_inheritable_accessor :x, :y
end

Foo.x = 1

class Bar < Foo
end

Bar.x #=> 1
Bar.x = 3
Bar.x #=> 3
Foo.x #=> 1

Подробнее здесь

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

person Chubas    schedule 16.08.2011
comment
Вау, это действительно странно! Итак, Bar использует x Foo, пока мы не попытаемся установить его, а затем пуф! он различает? Мне трудно понять, почему автор того документа, на который вы ссылаетесь, звучит так весело. :-) Иногда мы можем захотеть иметь переменную третьего типа, которая сочетает в себе преимущества первых двух, но без их недостатков: id est, переменная, которая передает значение по умолчанию по дереву наследования, но позволяет каждому подклассу изменять его в частном порядке. это: добро пожаловать в Rails class_inheritable_accessor! - person AlexChaffee; 28.09.2011

Никогда не используйте переменные класса в Ruby. Они вызывают проблемы с наследованием. Вместо этого всегда используйте переменные экземпляра класса.

person Marnen Laibow-Koser    schedule 16.08.2011
comment
Они не вызывают проблем с наследованием. Скорее, у них есть потенциально неожиданная семантика в отношении наследования — в частности, переменная класса является общей для всех подклассов класса, внутри которого она определена. Это может быть или не быть тем, что вы хотите, но это проблема, только если вы не знаете об этом. - person AlexChaffee; 28.09.2011
comment
Я имел в виду проблемы в смысле неразберихи. Действительно, они работают, но удивительным образом. - person Marnen Laibow-Koser; 30.09.2011
comment
О, я знаю. Я просто был педантичен. Если вы не можете быть педантичными в комментариях StackOverflow, то где вы можете? :-) - person AlexChaffee; 02.10.2011
comment
Голосую за явную язвительность. - person Marnen Laibow-Koser; 04.10.2011

У вас также есть возможность объявлять переменные экземпляра на уровне класса:

class Foo
    @bar = 'baz'

    def Foo.print
        puts @bar
    end
end

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

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

person Don Werve    schedule 16.08.2011
comment
Итак, нет никакого способа сделать наследуемую переменную, которая была бы закрытой? - person Zequez; 16.08.2011
comment
Переменные экземпляра класса являются закрытыми, как и любая другая переменная экземпляра. В Ruby нет такой вещи, как общедоступная переменная экземпляра. - person Marnen Laibow-Koser; 16.08.2011
comment
конфиденциальность не очень уважаемая концепция в Ruby: Foo.class_eval { @bar } вы попадаете прямо туда - person AlexChaffee; 28.09.2011
comment
Это правда, но eval должен подсказать вам, что вы делаете что-то потенциально опасное. - person Marnen Laibow-Koser; 20.10.2011

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

А теперь я объясню некоторые вещи в мучительных деталях, которые вы ожидаете от ответа Ruby на StackOverflow:

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

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

Вместо этого мы хотим определить методы для класса, а не для его экземпляров. На самом деле это означает, что эти методы находятся в списке методов экземпляра для класса-одиночки объекта класса. (Фу!)

Итак, подведем итог: существует по крайней мере четыре идиомы для переключения и определения методов в одноэлементном классе объекта класса Foo:

class Foo
  @a, @b, @c, @d = 1, 2, 3, 4

  # 1. pass the target to def
  def Foo.a
    @a
  end

  # 2. pass the target to def, relying on the fact that self 
  # happens to be the class object right now
  def self.b
    @b
  end

  # switch from class scope to singleton class scope
  class << self

    # 3. define a plain accessor in singleton class scope
    def c
      @c
    end

    # 4. use a macro to define an accessor
    attr_reader :d
  end

end

p [Foo.a, Foo.b, Foo.c, Foo.d]
#=> [1, 2, 3, 4]

(Есть, вероятно, еще полдюжины способов сделать это, если учесть class_eval и define_method и тому подобное, но пока это должно вас удовлетворить. :-))

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

class Bar < Foo
end

p [Bar.a, Bar.b, Bar.c, Bar.d]
#=> [nil, nil, nil, nil]
person AlexChaffee    schedule 28.09.2011