Java中“私有静态最终”和“公共静态最终”类变量的最接近的Ruby表示形式?

2022-09-03 03:33:59

给定下面的 Java 代码,在 Ruby 类中可以表示这两个变量的最接近的是什么?而且,在Ruby中是否有可能像Java中那样区分变量和变量?static finalprivate staticpublic static

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,而你真正应该做的是找出问题,然后弄清楚如何在语言B中解决它。

我真的不能确定你试图从那个小代码nippet中解决的问题是什么,但是这里有一个可能的想法,如何在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 中,就像在所有其他“proper”(对于“proper”的各种定义)中一样,面向对象的语言,状态(实例变量、字段、属性、槽、属性,无论你想调用它们什么)始终是私有的。没有办法从外面访问它们。与对象通信的唯一方法是向其发送消息。

[注意:每当我写“没有办法”,“总是”,“唯一的方式”等东西时,它实际上并不意味着“没有方法,除了反思”。例如,在这种特殊情况下,有 。Object#instance_variable_set

换句话说:在 Ruby 中,变量始终是私有的,访问它们的唯一方法是通过 getter 和/或 setter 方法,或者,正如它们在 Ruby 中所说的那样,是属性读取器和/或编写器。

现在,我继续写实例变量,但在Java示例中,我们有静态字段,即变量。好吧,在Ruby中,与Java不同,类也是对象。它们是类的实例,因此,就像任何其他对象一样,它们可以具有实例变量。因此,在 Ruby 中,类变量的等效变量实际上只是一个标准实例变量,它属于恰好是一个类的对象。Class

(还有类层次结构变量,用双引号表示。这些真的很奇怪,你可能应该忽略它们。类层次结构变量在整个类层次结构中共享,即它们所属的类,其所有子类及其子类及其子类...以及所有这些类的所有实例。实际上,它们更像是全局变量,而不是类变量。它们实际上应该被称为而不是,因为它们与全局变量的关系比实例变量更紧密。它们并非完全无用,但很少有用。@@sigil$$var@@var

所以,我们已经介绍了“字段”部分(Java字段==Ruby实例变量),我们已经涵盖了“公共”和“私有”部分(在Ruby中,实例变量始终是私有的,如果你想让它们公开,请使用公共的getter/setter方法),我们已经涵盖了“静态”部分(Java静态字段==Ruby类实例变量)。“最终”部分呢?

在Java中,“final”只是一种拼写“const”的有趣方式,设计人员避免了这种方式,因为C和C++等语言中的关键字被巧妙地破坏了,他们不想让人们感到困惑。Ruby 确实有常量(以大写字母开头表示)。不幸的是,它们并不是真正恒定的,因为试图修改它们,同时生成警告,实际上是有效的。因此,它们更像是一种约定,而不是编译器强制执行的规则。但是,常量更重要的限制是它们始终是公共的。const

因此,常量几乎是完美的:它们不能被修改(好吧,它们不应该被修改),即它们是,它们属于一个类(或模块),即它们是。但是它们总是,所以不幸的是它们不能用于模拟场。finalstaticpublicprivate static final

而这正是思考问题而不是解决方案的一点。你想要什么?您希望声明

  1. 属于一个类,
  2. 只能读不写,
  3. 仅初始化一次,并且
  4. 可以是私有的,也可以是公共的。

你可以实现所有这些,但方式与Java完全不同:

  1. 类实例变量
  2. 不提供 setter 方法,只提供 getter
  3. 使用 Ruby 的复合赋值仅赋值一次||=
  4. getter method

您唯一需要担心的是,您不会分配到任何地方,或者更好的是,根本不访问它。始终使用 getter 方法。@my_public_device

是的,这是实现中的一个漏洞。Ruby通常被称为“成人语言”或“同意的成人语言”,这意味着你不需要让编译器强制执行某些东西,而只是把它们放在文档中,只是相信你的开发人员已经学会了触摸其他人的隐私是不礼貌的......


一种完全不同的隐私方法是函数式语言中使用的方法:使用闭包。闭包是在其词法环境中关闭的代码块,即使在该词法环境超出范围之后也是如此。这种实现私有状态的方法在 Scheme 中非常流行,但最近也被 Douglas Crockford 等人推广为 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

请注意我的答案顶部版本的微妙但重要的区别:缺少sigil。在这里,我们创建的是局部变量,而不是实例变量。一旦类体结束,这些局部变量就会超出范围,并且再也无法访问。只有定义两个 getter 方法的两个块仍然可以访问它们,因为它们在类体上关闭。现在,它们确实是私有的,并且它们是 ,因为整个程序中唯一仍然可以访问它们的是纯粹的getter方法。@final

这可能不是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

这里的诀窍是,通过将设备封装到模块中,您只能通过该模块访问设备。从类中,无法修改要访问的设备,而无需直接对类或模块执行操作。根据您的需要,呼叫可能合适,也可能不合适。FooDeviceFinalDevicefreezeFinalDevice

如果要创建公共和专用访问器,可以按如下方式进行修改: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指出,正如 中声明的那样,确实可以通过 的实例访问。我懒洋洋地认为,由于它不在默认文本输出中,因此它不在里面。@finFinalDeviceFooFoo#inspectFoo

您可以通过更明确地创建模块的实例变量来解决此问题:@finFinalDevice

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

推荐