2009-12-02 34 views
3

我想在:是否可以比较Ruby中的私有属性?

class X 
    def new() 
     @a = 1 
    end 
    def m(other) 
     @a == [email protected] 
    end 
end 

x = X.new() 
y = X.new() 
x.m(y) 

但它没有工作。

的错误信息是:

syntax error, unexpected tIVAR 

如何从同一类比较两个私有属性呢?

+0

简短回答,当您调用实例变量时,不需要@。所以你需要other.a。 – 2009-12-02 16:29:30

+0

@Chuck Vose:我想不是所有的答案都必须是正确的。我写了'@a == other.a',解释器说:'NoMethodError:未定义的方法'a'为#' – OscarRyz 2009-12-02 17:28:56

+0

Ruby没有属性。 '@ a'是一个实例变量。 – 2012-03-28 08:01:03

回答

8

有几种方法

消气:

class X 
    attr_reader :a 
    def m(other) 
    a == other.a 
    end 
end 

instance_eval

class X 
    def m(other) 
    @a == other.instance_eval { @a } 
    end 
end 

instance_variable_get

class X 
    def m(other) 
    @a == other.instance_variable_get :@a 
    end 
end 

我不认为红宝石^ h作为“朋友”或“受保护”访问的概念,甚至“私人”很容易被盗用。使用getter会创建一个只读属性,而instance_eval意味着您必须知道实例变量的名称,所以内涵是相似的。

+0

了解'instance_eval'非常酷。起初我不相信你,所以我试了一下。像魅力一样工作。我需要回头去编程Ruby ......这是一种非常整齐的语言。 – 2009-12-02 03:19:11

+3

它确实有'保护'方法。我更新了我的答案以证明这一点。 – 2009-12-02 03:23:46

+1

哦,'保护'。这可能是最好的方法,因为语法更自然。 – 2009-12-02 03:49:39

4

如果不使用instance_eval选项(如@jleedev发布),并选择使用getter方法,你仍然可以保持它protected

如果你想在Ruby protected方法,只是做了下面创建一个只能从同一个类的对象读取一个getter:

class X 
    def new() 
     @a = 1 
    end 
    def m(other) 
     @a == other.a 
    end 

    protected 
    def a 
     @a 
    end 
end 

x = X.new() 
y = X.new() 
x.m(y) # Returns true 
x.a  # Throws error 
+0

请注意,这里有一个竞争条件:首先将attr_reader创建为public,* then *,作为单独的步骤,它会被保护。另一个线程可以在这两个步骤之间调用访问器。我的编辑没有这种竞争条件,因为它切换到受保护*第一*和*然后*创建访问者。无论如何,这个编辑仍然比原作者的版本更好,甚至不是有效的Ruby语法。 – 2009-12-04 10:00:44

+0

@Jorg,感谢您收集冒号问题,但请不要重写我的代码。另外,同意其他编辑的竞赛条件。我也回滚了他的变化。 – 2009-12-04 14:48:48

11

这儿已经出现了几个很好的答案,立即解决问题,但我注意到你的代码的其他一些作品是值得评论。 (他们中的大多数琐碎,虽然)。

以下四个平凡的人,他们都涉及到编码风格:

  1. 缩进:你混合4个空格缩进和5位。通常最好坚持一个缩进样式,而在Ruby中通常是2个空格。
  2. 如果一个方法没有使用任何参数,通常会在方法定义中省略缺点。
  3. 同样,如果你发送一个没有参数的消息,那么这些缺少的东西就没有了。
  4. 打开设备之后和关闭设备之前不得有空格,除非是块。

无论如何,这只是小东西。大的东西是这样的:

def new 
    @a = 1 
end 

这并不做你认为它!这定义了一种称为X#new的方法方法称为X.new的类方法!

你在这里叫什么:

x = X.new 

方法叫new,它已经从Class类继承。所以,你永远不打电话给你的新方法,这意味着@a = 1从来没有得到执行,这意味着@a永远是不确定的,这意味着它将始终评估为nil这意味着self@aother@a永远是这意味着m相同将永远是true

你可能想要做的是提供一个构造函数,除了Ruby不具有构造函数。 Ruby只使用工厂方法。

真的想要覆盖的方法是实例方法initialize。现在你可能会问自己:“当我实际拨打方法时,为什么我必须重写实例方法initialize?”new

好,施工对象在Ruby中是这样工作的:对象建设分为两个阶段,分配初始化。分配是通过名为allocate的公共类方法完成的,该方法被定义为类Class的实例方法,并且通常是从不重写。它只是为对象分配内存空间并设置了几个指针,但是此时对象并不是真正可用的。

这就是初始化程序进来的地方:它是一个名为initialize的实例方法,它设置对象的内部状态并将其引入一个可以被其他对象使用的一致的,完全定义的状态。

因此,为了充分创建新的对象,你需要做的是这样的:

x = X.allocate 
x.initialize 

[注:Objective-C的程序员可能会认识到这一点。]

然而,因为它太容易忘记调用initialize,并作为一般规则的对象应该是施工后完全有效的,有一个叫Class#new便利工厂方法,它完成了所有的工作,为您和看起来像这样:

class Class 
    def new(*args, &block) 
    obj = alloc 
    obj.initialize(*args, &block) 

    return obj 
    end 
end 

[注:实际上,initialize是私有的,所以反射有可能被用来规避这样的访问限制:obj.send(:initialize, *args, &block)]

最后,让我解释一下什么错在你m会见HOD。 (其他人已经解释了如何解决它。)

在Ruby中,没有办法(注意:在Ruby中,“没有任何办法”实际上转换为“总是有一种涉及反射的方式”)来访问来自实例外部的实例变量。这就是为什么它毕竟被称为实例变量,因为它属于实例。这是Smalltalk的遗留问题:在Smalltalk中没有可见性限制,全部方法是公共的。因此,实例变量只是在Smalltalk中进行封装的方式,毕竟,封装是面向对象的支柱之一。在Ruby中,有可见性限制(例如,我们已经在上面看到过,例如),所以不必为此隐藏实例变量。另一个原因是:统一访问原则。

的UAP指出,如何使用功能应该是独立于功能是如何实现的。因此,访问功能应始终保持一致,即统一。原因是该功能的作者可以自由地更改该功能在内部的工作方式,而不会中断该功能的用户。换句话说,它是基本的模块化。

这意味着例如获取集合的大小应该始终相同,无论大小是否存储在变量中,每次动态计算,第一次懒惰地计算,然后存储在变量中,memoized管他呢。听起来很明显,但例如Java的得到这个错误:

obj.size # stored in a field 

obj.getSize() # computed 

红宝石取出的简单方法。在Ruby中,只有一个方式使用一项功能:发送消息。由于只有一种方式,访问是统一的。

因此,长话短说:你根本无法访问另一个实例的实例变量。你只能通过消息发送与该实例进行交互。这意味着另一个对象必须为您提供一种访问其实例变量的方法(在这种情况下至少为protected可见性),或者您必须违反该对象的封装(并因此失去统一访问,增加耦合并面临未来风险破损)通过使用反射(在这种情况下instance_variable_get)。

这是在其所有的荣耀:

#!/usr/bin/env ruby 

class X 
    def initialize(a=1) 
    @a = a 
    end 

    def m(other) 
    @a == other.a 
    end 

    protected 

    attr_reader :a 
end 

require 'test/unit' 
class TestX < Test::Unit::TestCase 
    def test_that_m_evaluates_to_true_when_passed_two_empty_xs 
    x, y = X.new, X.new 
    assert x.m(y) 
    end 
    def test_that_m_evaluates_to_true_when_passed_two_xs_with_equal_attributes 
    assert X.new('foo').m(X.new('foo')) 
    end 
end 

或者:

class X 
    def m(other) 
    @a == other.instance_variable_get(:@a) 
    end 
end 

其中这两个你选择的一个是personly口味的问题,我会说。该Set类标准库使用反射的版本,虽然它使用代替instance_eval:。

class X 
    def m(other) 
    @a == other.instance_eval { @a } 
    end 
end 

(我不知道为什么也许instance_variable_get根本没当Set写有红宝石是要在2月17岁时,stdlib中的一些内容是从最初的日子开始的。)

+0

这是一个很棒的答案。我还没有看到有人在很长一段时间内对这个问题付出了太多的努力。谢谢! – 2009-12-02 16:28:48

+1

+1纯粹的长度;-) – 2009-12-02 22:04:20

+0

* ...你可能会问自己:“当我实际调用一个名为'new'的类方法时,为什么我必须重写一个名为'initialize'的实例方法? “*哈哈哈......我从来没有问过我的那个。我以为是*哦,所以它是'初始化'然后*。感谢你的回答,我每两天阅读一次(直到我得到它);) – OscarRyz 2009-12-04 22:47:28

相关问题