2009-12-02 69 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