Ближайшее представление Ruby переменной private private final final и public public final 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[])
  {
   ...
  }
}

Ответ 1

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

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

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

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

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

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

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

Итак, мы рассмотрели раздел "поле" (переменная Java field == Ruby), мы рассмотрели части "public" и "private" (в Ruby переменные экземпляра всегда являются частными, если вы хотите сделать они общедоступны, используют общедоступный метод getter/setter), и мы рассмотрели "статическую" часть (Java static field == Ruby class instance variable). Как насчет "финальной" части?

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

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

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

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

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

  • переменная экземпляра класса
  • не предоставляют метод setter, только getter
  • использовать составное назначение Ruby ||= для назначения только один раз
  • метод getter

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

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


Совершенно другой подход к конфиденциальности - это тот, который используется в функциональных языках: используйте закрытие. Закрытие - это блоки кода, которые близки к их лексической среде, даже после того, как лексическая среда вышла за рамки. Этот метод реализации частного государства очень популярен в Схеме, но недавно был популяризирован Дугласом Крокфордом и др. для JavaScript. Вот пример в Ruby:

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, потому что единственная вещь во всей программе, которая все еще имеет к ним доступ, - это чистый метод getter.

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

Ответ 2

Ближайшая вещь, которую я могу придумать для конечной переменной, - поставить рассматриваемую переменную как переменную экземпляра модуля:

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

Ответ 3

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

И да, require 'device' при необходимости.

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

Ответ 4

private static в Ruby:

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

public static в Ruby:

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

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

Ruby не может иметь "final":)