Ближайшее представление в Ruby переменных класса private static final и public static final в Java? - PullRequest
16 голосов
/ 14 марта 2010

Учитывая приведенный ниже 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[])
  {
   ...
  }
}

Ответы [ 4 ]

45 голосов
/ 14 марта 2010

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

Однако похоже, что вы делаете одну из классических ошибок переноса: у вас есть решение на языке A и вы пытаетесь перевестичто на языке B, когда вы действительно должны выяснить проблему , а затем выяснить, как решить ее на языке 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 переменные всегда являются закрытыми, единственный способ получить к ним доступ через метод получения и / или установки, или, поскольку они вызываются в Ruby, считывателе атрибутов и / или модуле записи.

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

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

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

В Java «финальная» - это просто забавный способ написания «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 часто называют «языком взрослого» или «языком взрослых для согласия», что означает, что вместо того, чтобы компилятор предписывал определенные вещи, вы просто помещаете их в документацию и просто полагаете, что ваши коллеги-разработчики узнали, что касаются других рядовые люди грубы ...


Совершенно другой подход к конфиденциальности - тот, который используется в функциональных языках: используйте замыкания. Замыкания - это блоки кода, которые закрываются в своей лексической среде, даже после того, как эта лексическая среда вышла из области видимости. Этот метод реализации частного государства очень популярен в Схеме, но в последнее время он также популяризируется Дугласом Крокфордом и соавт. для 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, потому что единственная вещь во всей программе, которая все еще имеет к ним доступ, это чистый метод получения .

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

3 голосов
/ 14 марта 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
1 голос
/ 14 марта 2010
class DeviceController
  MY_DEVICE = Device.get_device("mydevice")
end

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

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

0 голосов
/ 14 марта 2010

приватная статика в 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 не может быть 'final':)

...