Подтвердить что ты не робот

Как сделать переменные экземпляра приватными в Ruby?

Есть ли способ сделать переменные экземпляра "private" (определение на С++ или Java) в ruby? Другими словами, я хочу, чтобы следующий код привел к ошибке.

class Base
  def initialize()
    @x = 10
  end
end

class Derived < Base
  def x
    @x = 20
  end
end

d = Derived.new
4b9b3361

Ответ 1

Как и большинство вещей в Ruby, переменные экземпляра не являются действительно "private" и могут быть доступны любому, у кого есть d.instance_variable_get :@x.

В отличие от Java/С++, переменные экземпляра в Ruby всегда являются частными. Они никогда не являются частью общедоступного API, как методы, так как к ним можно получить доступ только с помощью этого подробного getter. Поэтому, если в вашем API есть какая-либо здравомыслие, вам не нужно беспокоиться о том, что кто-то злоупотребляет вашими переменными экземпляра, поскольку вместо этого они будут использовать методы. (Конечно, если кто-то хочет разобраться и получить доступ к приватным методам или переменным экземпляра, не существует способа их остановить.)

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

Ответ 2

Никогда не используйте переменные экземпляра напрямую. Только когда-либо пользуйтесь аксессуарами. Вы можете определить читателя как публичный, так и частный писатель:

class Foo
  attr_reader :bar

  private

  attr_writer :bar
end

Однако имейте в виду, что private и protected не означают, что вы думаете о них. Публичные методы могут быть вызваны против любого получателя: named, self или implicit (x.baz, self.baz или baz). Защищенные методы могут быть вызваны только с помощью приемника self или неявно (self.baz, baz). Частные методы могут быть вызваны только с неявным приемником (baz).

Короче говоря, вы приближаетесь к проблеме с точки зрения не-Ruby. Всегда используйте аксессоры вместо переменных экземпляра. Используйте public/protected/private для документирования своих намерений и предполагайте, что потребители вашего API являются ответственными взрослыми.

Ответ 3

Есть два разных элемента этого поведения. Первый хранит x в только для чтения значение, а второй защищает геттер от изменения в подклассах.


Значение только для чтения

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

class Foo
  def initialize (x)
    define_singleton_method(:x) { x }
  end
end

Начальное значение x теперь заблокировано внутри блока, который мы использовали для определения getter #x, и никогда не может быть доступен, за исключением вызова foo.x, и его нельзя изменить.

foo = Foo.new(2)
foo.x  # => 2
foo.instance_variable_get(:@x)  # => nil

Обратите внимание, что он не сохраняется как переменная экземпляра @x, но она все еще доступна через getter, который мы создали, используя define_singleton_method.


Защита геттера

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

class Foo
  def self.method_added (name)
    raise(NameError, "cannot change x getter") if name == :x
  end
end

class Bar < Foo
  def x
    20
  end
end

# => NameError: cannot change x getter

Это очень тяжелый метод защиты геттера.

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

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

Я знаю, что это старо, но я столкнулся с ситуацией, когда я не так сильно хотел предотвратить доступ к @x, я действительно хотел исключить его из любых методов, которые используют отражение для сериализации. В частности, я часто использую YAML::dump для целей отладки, а в моем случае @x имеет класс Class, который YAML::dump отказывается сбрасывать.

В этом случае я рассмотрел несколько вариантов

  • Адресация этого только для yaml путем переопределения "to_yaml_properties"

    def to_yaml_properties
      super-["@x"]
    end
    

    но это сработало бы только для yaml, и если бы другие самосвалы (to_xml?) не были бы счастливы

  • Адресация для всех пользователей отражения путем переопределения "экземпляров_переменных"

    def instance_variables
      super-["@x"]
    end
    
  • Кроме того, я нашел этот в одном из моих поисков, но не протестировал его, поскольку приведенное выше кажется более простым для моих нужд

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