Java中“private static final”和“public static final”类变量的最近Ruby表示?

鉴于下面的Java代码,你可以在Ruby类中代表这两个static final变量的最接近的代码是什么? 并且,在Ruby中是否可以区分private static public static变量和public 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[]) { ... } } 

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_setObject#instance_variable_set

换句话说:在Ruby中,变量总是私有的,访问它们的唯一方法是通过getter和/或setter方法,或者在Ruby中调用属性读取器和/或编写器。

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

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

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

在Java中,“final”只是拼写“const”的一种有趣方式,设计者避免使用它,因为C和C ++等语言中的const关键字被巧妙地打破,并且他们不想让人混淆。 Ruby 确实有常量(以大写字母开头表示)。 不幸的是,它们并不是真正的常量,因为在生成警告的同时尝试修改它们实际上是有效的。 因此,它们更像是一种约定而不是编译器强制规则。 然而,常数的更重要的限制是它们总是公开的。

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

这正是思考问题而不是解决方案的关键所在。你想要的是什么? 你想要那样的状态

  1. 属于一个class级,
  2. 只能读不写,
  3. 只被初始化一次
  4. 可以是私人的也可以是公共的。

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

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

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

是的,这实施中的一个漏洞。 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方法的两个块仍然可以访问它们,因为它们靠近类体。 现在,他们真的是私人的他们是final ,因为整个程序中仍然可以访问它们的唯一东西是纯粹的getter方法。

这可能不是惯用的Ruby,但对于任何具有Lisp或JavaScript背景的人来说,它应该足够清楚。 它也很优雅。

我能想到最终变量的最接近的事情是将有问题的变量作为模块的实例变量:

 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) 

这输出:

 # true 

这里的技巧是通过将设备封装到模块中,您只能通过该模块访问设备。 从Foo类中,无法直接操作Device类或FinalDevice模块,无法修改您正在访问的Device 。 根据您的需要, FinalDevicefreeze调用可能适合也可能不适合。

如果要创建公共和私有访问器,可以像这样修改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 
 class DeviceController MY_DEVICE = Device.get_device("mydevice") end 

是的,如果需要, require 'device'

虽然没有什么可以阻止你重新定义其他地方的常量,除了警告:)

Ruby中的私有静态:

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

Ruby中的public static:

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

Ruby可以没有“最终”:)