Как сделать переменные экземпляра частными в Ruby? - PullRequest
33 голосов
/ 25 января 2010

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

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

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

d = Derived.new

Ответы [ 6 ]

35 голосов
/ 25 января 2010

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

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

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

26 голосов
/ 24 января 2012

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

class Foo
  attr_reader :bar

  private

  attr_writer :bar
end

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

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

13 голосов
/ 18 февраля 2014

Можно (но нецелесообразно) делать именно то, что вы просите.

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


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

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

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

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

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

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


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

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

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.

5 голосов
/ 26 января 2010

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

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

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

1 голос
/ 25 января 2012

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

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

0 голосов
/ 09 мая 2012

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

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

  1. Решение этой проблемы только для yaml путем переопределения «to_yaml_properties»

    def to_yaml_properties
      super-["@x"]
    end
    

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

  2. Адресация для всех пользователей отражений путем переопределения «instance_variables»

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

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

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