Ближайшее Ruby-представление переменной класса "private static final" и "public static final" в Java?

Учитывая приведенный ниже код Java, как наиболее близко вы могли бы представить эти две переменные static final в классе Ruby? И возможно ли в Ruby различать переменные private static и public static, как в Java?

public class DeviceController
{
  ...
  private static final Device myPrivateDevice = Device.getDevice("mydevice");
  public static final Device myPublicDevice = Device.getDevice("mydevice");
  ...
  public static void main(String args[])
  {
   ...
  }
}

person Hosh    schedule 14.03.2010    source источник
comment
Насколько я знаю, в Ruby нет реальных констант (окончательных)   -  person Anantha Kumaran    schedule 14.03.2010
comment
но можно использовать SOME_CONSTANT.freeze, чтобы сделать объект неизменным   -  person clyfe    schedule 14.03.2010
comment
Как насчет этого решения stackoverflow.com/a/18168745/665967   -  person meso_2600    schedule 11.08.2013


Ответы (4)


В Ruby действительно нет эквивалентной конструкции.

Однако похоже, что вы совершаете одну из классических ошибок переноса: у вас есть решение на языке А, и вы пытаетесь перевести его на язык Б, тогда как на самом деле вам нужно выяснить задачу, а затем выяснить, как решить ее на языке Б.

Я не могу точно сказать, какую проблему вы пытаетесь решить с помощью этого небольшого фрагмента кода, но вот одна возможная идея, как реализовать это в Ruby:

class DeviceController
  class << self
    def my_public_device;  @my_public_device  ||= Device['mydevice'] end

    private

    def my_private_device; @my_private_device ||= Device['mydevice'] end
  end
end

Вот еще:

class DeviceController
  @my_public_device  ||= Device['mydevice']
  @my_private_device ||= Device['mydevice']

  class << self
    attr_reader :my_public_device, :my_private_device
    private :my_private_device
  end
end

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

Давайте рассмотрим здесь некоторые понятия.

В Ruby, как и в любом другом «правильном» (для различных определений «правильного») объектно-ориентированном языке, состояние (переменные экземпляра, поля, свойства, слоты, атрибуты, как бы вы их ни называли) всегда< /em> частный. Доступ к ним извне невозможен. Единственный способ общаться с объектом — отправлять ему сообщения.

[Примечание: Всякий раз, когда я пишу что-то вроде «ни за что», «всегда», «единственный способ» и т. д., это на самом деле не означает «ни за что, кроме как для размышления». В данном конкретном случае, например, Object#instance_variable_set.]

Другими словами: в Ruby переменные всегда являются приватными, единственный способ получить к ним доступ — использовать метод получения и/или установки, или, как они называются в Ruby, считыватель и/или писатель атрибутов.

Теперь я продолжаю писать о переменных экземпляра, но в примере Java у нас есть статические поля, то есть переменные класса. Что ж, в Ruby, в отличие от Java, классы тоже являются объектами. Они являются экземплярами класса Class, поэтому, как и любой другой объект, они могут иметь переменные экземпляра. Таким образом, в Ruby эквивалентом переменной класса является стандартная переменная экземпляра, которая принадлежит объекту, который просто является классом.

(Есть также переменные иерархии классов, обозначенные двойным знаком @@sigil. Это действительно странно, и вам, вероятно, следует просто игнорировать их. Переменные иерархии классов являются общими для всей иерархии классов, то есть класса, к которому они принадлежат, всех его подклассов. и их подклассы и их подклассы ... а также все экземпляры всех этих классов. На самом деле, они больше похожи на глобальные переменные, чем на переменные класса. Их действительно следует называть $$var вместо @@var, поскольку они гораздо более тесно связаны с глобальными переменные, чем переменные экземпляра. Они не совсем бесполезны, но очень редко полезны.)

Итак, мы рассмотрели часть «поля» (поле Java == переменная экземпляра Ruby), мы рассмотрели «общедоступную» и «частную» части (в Ruby переменные экземпляра всегда являются частными, если вы хотите сделать их общедоступными, использовать общедоступный метод получения/установки), и мы рассмотрели «статическую» часть (статическое поле Java == переменная экземпляра класса Ruby). А как насчет "заключительной" части?

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

Итак, константы почти идеальны: их нельзя изменить (ну, не следует изменять), т.е. они final, они принадлежат классу (или модулю), т.е. они static. Но они всегда public, поэтому, к сожалению, их нельзя использовать для моделирования private static final полей.

И это именно тот момент, когда нужно думать о проблемах, а не о решениях. Чего вы хотите? Вы хотите заявить, что

  1. принадлежит классу,
  2. можно только читать, а не писать,
  3. инициализируется только один раз и
  4. может быть как частным, так и публичным.

Вы можете добиться всего этого, но совершенно другим способом, чем в Java:

  1. переменная экземпляра класса
  2. не предоставляйте метод установки, только метод получения
  3. используйте составное назначение Ruby ||=, чтобы назначить только один раз
  4. геттерный метод

Единственное, о чем вам нужно беспокоиться, это то, что вы нигде не назначаете @my_public_device, или, что еще лучше, вообще не получаете к нему доступ. Всегда используйте метод получения.

Да, это это дыра в реализации. Ruby часто называют «языком взрослых» или «языком взрослых с согласия», что означает, что вместо того, чтобы компилятор навязывал определенные вещи, вы просто помещаете их в документацию и просто верите, что ваши коллеги-разработчики усвоили, что касаться других личные сообщения людей грубы...


Совершенно другой подход к конфиденциальности используется в функциональных языках: используйте замыкания. Замыкания — это блоки кода, которые закрываются в своем лексическом окружении даже после того, как это лексическое окружение выходит за рамки. Этот метод реализации частного состояния очень популярен в Scheme, но недавно его также популяризировали Douglas Crockford et al. для JavaScript. Вот пример на Руби:

class DeviceController
  class << self
    my_public_device, my_private_device = Device['mydevice'], Device['mydevice']

    define_method :my_public_device  do my_public_device  end
    define_method :my_private_device do my_private_device end

    private :my_private_device
  end # <- here the variables fall out of scope and can never be accessed again
end

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

Это, вероятно, не идиоматический Ruby, но для любого, кто знаком с Lisp или JavaScript, это должно быть достаточно ясно. Это также очень элегантно.

person Jörg W Mittag    schedule 14.03.2010
comment
Отличный ответ! В последнем примере использование ||= кажется излишним, и должно быть достаточно простого присваивания? - person Lars Haugseth; 15.03.2010

Самое близкое, что я могу придумать для конечной переменной, — это поместить рассматриваемую переменную в качестве переменной экземпляра модуля:

class Device
    # Some static method to obtain the device
    def self.get_device(dev_name)
        # Need to return something that is always the same for the same argument
        dev_name
    end
end

module FinalDevice
    def get_device
        # Store the device as an instance variable of this module...
        # The instance variable is not directly available to a class that
        # includes this module.
        @fin ||= Device.get_device(:my_device).freeze
    end
end

class Foo
    include FinalDevice
    def initialize
        # Creating an instance variable here to demonstrate that an
        # instance of Foo cannot see the instance variable in FinalDevice,
        # but it can still see its own instance variables (of course).
        @my_instance_var = 1
    end
end

p Foo.new

p (Foo.new.get_device == Foo.new.get_device)

Это выводит:

#<Foo:0xb78a74f8 @my_instance_var=1>
true

Хитрость здесь в том, что, инкапсулируя устройство в модуль, вы можете получить доступ к устройству только через этот модуль. Из класса Foo невозможно изменить какое устройство, к которому вы обращаетесь, без прямого воздействия на класс Device или модуль FinalDevice. Вызов freeze в FinalDevice может быть или не быть подходящим, в зависимости от ваших потребностей.

Если вы хотите сделать общедоступный и частный доступ, вы можете изменить Foo следующим образом:

class Foo
    include FinalDevice

    def initialize
        @my_instance_var = 1
    end

    def get_device_public
        get_device
    end

    private
    def get_device_private
        get_device
    end

    private :get_device
end

В этом случае вам, вероятно, потребуется изменить FinalDevice::get_device, чтобы он также принимал аргумент.

Обновление: @banister указал, что @fin, объявленный в FinalDevice, действительно доступен для экземпляра Foo. Я лениво предположил, что, поскольку его не было в текстовом выводе по умолчанию Foo#inspect, его не было и внутри Foo.

Вы можете исправить это, более явно сделав @fin переменной экземпляра модуля FinalDevice:

class Device
    def self.get_device(dev_name)
        dev_name
    end
end

module FinalDevice
    def get_device
        FinalDevice::_get_device
    end

    protected
    def self._get_device
        @fin ||= Device.get_device(:my_device).freeze
    end
end

class Foo
    include FinalDevice

    def get_device_public
        get_device
    end

    def change_fin
        @fin = 6
        @@fin = 8
    end

    private
    def get_device_private
        get_device
    end

    private :get_device
end

f = Foo.new
x = f.get_device_public
f.change_fin
puts("fin was #{x}, now it is #{f.get_device_public}")

Что правильно выводит:

fin was my_device, now it is my_device
person Mark Rushakoff    schedule 14.03.2010
comment
+1, очень элегантно. Кроме того, спасибо за то, что вдохновили мое решение на основе закрытия! - person Jörg W Mittag; 14.03.2010
comment
что вы имеете в виду, что ивар не доступен напрямую в классе? если вы определите метод в Foo, такой как def change_fin; @fin = 100; end, а затем запустите f = Foo.new; f.change_fin; f.get_device_public, возврат будет 100. Вы можете просто получить доступ к @fin в классе, и он изменит точно тот же самый @fin, который определен в модуле. Объект имеет только одну таблицу ivar. - person horseyguy; 14.03.2010
comment
@banister: ты был прав; Я обновил код, чтобы явно использовать переменную экземпляра модуля FinalDevice. Я лениво предположил, что, поскольку @fin не отображается в p Foo.new, он уже содержится в модуле FinalDevice. Но теперь очевидно, что он просто еще не был объявлен. Спасибо. - person Mark Rushakoff; 14.03.2010

class DeviceController
  MY_DEVICE = Device.get_device("mydevice")
end

И да, require 'device' если нужно.

Хотя ничто не помешает вам переопределить константу где-то еще, кроме предупреждения :)

person alex.zherdev    schedule 14.03.2010
comment
на самом деле он является общедоступным :) вы можете использовать DeviceController::MY_DEVICE из всех мест вашей программы. Не знаю, как сделать его приватным. - person alex.zherdev; 14.03.2010
comment
приватные константы обсуждались в Ruby 2.0. - person Jörg W Mittag; 07.10.2011

частный статический в Ruby:

class DeviceController
    @@my_device = Device.get_device("mydevice")
end

общедоступная статика в Ruby:

class DeviceController       
    def self.my_device; @@my_device; end

    @@my_device = Device.get_device("mydevice")
end

У Ruby не может быть «финала» :)

person horseyguy    schedule 14.03.2010