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

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

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

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

d = Derived.new

person prasadvk    schedule 25.01.2010    source источник
comment
Это кажется немного необычным запросом, как можно использовать такой шаблон? Возможно, вы знаете что-то, чего не знаю я, это может пригодиться в будущем.   -  person Tim Snowhite    schedule 26.01.2010
comment
Исходя из мира C++, мне кажется естественным иметь закрытые переменные в базовом классе, к которым нельзя получить доступ в производном классе, и это дает мне уверенность в том, что они не будут изменены в производном классе. В приведенном выше примере я могу быть уверен, что единственное место, где @x будет изменено, находится в классе Base, если возможно сделать его частной переменной экземпляра.   -  person prasadvk    schedule 26.01.2010
comment
Я думаю, вам не следует пытаться кодировать C++ в Ruby. Поскольку Ruby — очень динамичный и мощный язык, всегда будет способ добраться до личных данных.   -  person Geo    schedule 27.01.2010
comment
Не могли бы вы дать мне более конкретный вариант его использования? Это не должно быть сложным. Я чувствую, что если бы я понял одну проблему, для которой вы не хотели бы, чтобы объект мог получить доступ к своим собственным слотам, это могло бы помочь обсуждению.   -  person Tim Snowhite    schedule 27.01.2010


Ответы (6)


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

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

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

person Josh Lee    schedule 25.01.2010
comment
разве проблема здесь не в том, что в своем коде он может изменить переменную @x из производного класса? Это противоречит тому, что в C++, где производный класс не может получить доступ к закрытым элементам данных. Таким образом, несмотря на то, что «переменные экземпляра в ruby ​​являются частными», важным моментом является то, что это другой вид частного, отличный от значения частного в C++. - person horseyguy; 26.01.2010
comment
Я думаю, что на языке С++ можно было бы сказать, что «переменные экземпляра в ruby ​​​​всегда защищены». Хотя это и не идеальный аналог, он более точен, чем значение private в C++. - person Spain Train; 14.11.2012
comment
вздох, да... присоединяйтесь к клубу языков сценариев, которые не могут должным образом реализовать поддержку ООП. - person ; 25.05.2014
comment
Как уже упоминал Horseguy в предыдущем комментарии, переменные экземпляра в Ruby всегда являются приватными операторами, а приватные означает, что они не доступны напрямую с использованием ‹obj_name›.‹attrib_name› вне класса. Однако вы можете использовать метод instance_variable_get() для доступа к атрибуту извне класса, и дочерние классы могут получить доступ к атрибутам. В терминологии ООП (и C++) атрибуты в Ruby будут защищенными (если вы игнорируете метод доступа instance_variable_get()) или общедоступными (если вы этого не сделаете). - person Alan Evangelista; 21.11.2018
comment
использовал некоторое время, теперь делюсь. - person Amol Pujari; 13.09.2019

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

class Foo
  attr_reader :bar

  private

  attr_writer :bar
end

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

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

person Stephen Touset    schedule 24.01.2012
comment
Часть о доступности и приемниках действительно помогла прояснить некоторые проблемы, с которыми я сталкивался в прошлом. - person Andy; 13.12.2012
comment
Никогда не используйте переменные экземпляра напрямую... Почему бы и нет? Они являются центральной частью языка. Я бы сказал, что это зависит от вашей ситуации и проблемы, которую вы пытаетесь решить. - person mastaBlasta; 07.11.2016
comment
Это эмпирическое правило. Конечно, attr_reader и attr_writer за кулисами используются переменные экземпляра. И вы можете использовать их напрямую для прозрачного запоминания (@_foo ||= begin; # slow operation; end). Но если вы используете переменные экземпляра напрямую, вы не можете подключиться к их поведению при получении или установке их значений без изменения кода где-либо еще (включая код, который их подклассы). Вы также не получите исключения, если ошибетесь в написании @isntance_variable, в то время как вы делаете это для self.mtehod(). Они не более важны, чем @@class_variables, которые также запрещены. - person Stephen Touset; 08.11.2016

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

Есть два разных элемента желаемого поведения. Первый хранит 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.

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.

person user513951    schedule 18.02.2014
comment
Имейте в виду, что определение метода приведет к аннулированию кеша методов ruby. Если вы создаете их много, это может отрицательно сказаться на производительности. - person Kelvin; 24.04.2015
comment
@Kelvin, это действительно отличный момент, спасибо. Всем, кто хочет узнать больше об этом снижении производительности в Ruby, следует прочитать эту замечательную статью: github.com/charliesome/charlie.bz/blob/master/posts/ - person user513951; 24.04.2015

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

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

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

person bryantsai    schedule 26.01.2010

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

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

person Andrew Grimm    schedule 24.01.2012
comment
+1. в большинстве случаев композиция обеспечивает более гибкое решение. Было бы неплохо, если бы производный класс не получал доступа к закрытым переменным-членам для защиты от случая, когда разработчик случайно повторно использует имя переменной, но опять же, предварительное объявление переменной в Ruby все равно не требуется. - person Chris Betti; 03.02.2012
comment
Первое утверждение Эндрю настолько верно, что программисты, пришедшие с Java/C++, должны вытатуировать его на своих руках! Классы не "объявляют" переменные экземпляра. Переменные экземпляра добавляются к объектам по мере выполнения программы. Если метод(ы), которые создают переменную экземпляра, не вызываются, объект никогда не будет иметь эту переменную экземпляра. - person ComDubh; 07.10.2016

Я знаю, что это старо, но я столкнулся со случаем, когда я не столько хотел предотвратить доступ к @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. Кроме того, я нашел это в одном из моих поисков, но не проверял его, так как приведенное выше кажется проще для моего потребности

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

person nhed    schedule 08.05.2012
comment
Я предлагаю задать это как отдельный вопрос и ответить на него самостоятельно. Ответ здесь создает дополнительный шум. - person Kelvin; 24.04.2015
comment
@Kelvin Я ответил здесь, потому что было не совсем понятно, ПОЧЕМУ ОП хотел это сделать, но это помогло бы ему, если бы его причины были похожи на мои. Он никогда не называл своих причин, если бы он это сделал и его основная цель была другой, я бы удалил ее. Как бы то ни было, это поможет любому, кто задаст этот вопрос, пытаясь решить конкретный вариант использования. Я не думаю, что для меня правильно задавать вопрос, на который я уже знаю ответ (хотя отвечать на собственные вопросы, очевидно, нормально) - person nhed; 24.04.2015