16

给定下面的 Java 代码,您可以static final在 Ruby 类中表示这两个变量的最接近的值是什么?而且,在 Ruby 中是否可以像在 Java 中那样区分private 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[])
  {
   ...
  }
}
4

4 回答 4

45

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 中,变量始终是私有的,访问它们的唯一方法是通过 getter 和/或 setter 方法,或者,正如它们在 Ruby 中所调用的,属性读取器和/或写入器。

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

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

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

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

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

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

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

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

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

您唯一需要担心的是,您没有分配到@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

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

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

于 2010-03-14T15:32:41.653 回答
3

我能想到的最接近最终变量的方法是将相关变量作为模块的实例变量:

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,就无法修改您正在访问的设备。根据您的需要,调用可能合适,也可能不合适。DeviceFinalDevicefreezeFinalDevice

如果要创建公共和私有访问器,可以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正如 in 声明的那样,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
于 2010-03-14T15:17:12.857 回答
1
class DeviceController
  MY_DEVICE = Device.get_device("mydevice")
end

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

尽管没有什么能阻止您在其他地方重新定义常量,除了警告:)

于 2010-03-14T07:49:19.600 回答
0

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' :)

于 2010-03-14T12:37:24.150 回答