12 種方式輕鬆實現 Ruby 調用

csdn 發佈 2020-08-22T07:11:46+00:00

頭圖 | CSDN 下載自東方IC。我們開玩笑說 Python 之所以能夠堅持這種思想,正是因為在 Python 中做每件事都只有一種正確的方法。

作者 | Gregory Witek

譯者 | 彎月,責編 | 王曉曼

頭圖 | CSDN 下載自東方IC

出品 | CSDN(ID:CSDNnews)

以下為譯文:

最近,與同事聊天的時候,我們談到了有關 Python 編程的某些方面。我們開玩笑說 Python 之所以能夠堅持這種思想,正是因為在 Python 中做每件事都只有一種正確的方法(針對 Python 語言而言,Python 庫可不一定)。這不禁讓我想到了 Ruby,其編程思想恰恰相反,一切都可以通過許多不同的方式完成。

因此,今天我就來整理一下,在 Ruby 中調用某個方法究竟有多少種方式。最終我找到了12種不同的方式(有一些方式略微有點牽強)。下面我們就來逐一介紹,請做好準備不要太過於吃驚哦,尤其是最後一個肯定會震撼到你!

注意:本文中的代碼不適合在生產中使用(尤其是最後3個示例)。這只是我對 Ruby 語言功能的探索。雖然有些技巧在很多情況下都可以派上用場,但請務必謹慎使用。簡單性、安全性和可讀性遠比花哨更為重要。

準備工作

為了進行此次實驗,我準備了一個類,其中包含了一個方法,下面我將通過多種不同的方式來調用這個方法。為了簡單起見,該方法不接受任何參數(不過即使加上參數,每個示例也可以正常工作)。

這個類叫做 User,有一個屬性 name,等待被調用的方法名叫 hello,調用這個方法將顯示一條歡迎信息,其中包含用戶名。

class User

def initialize(name)

@name = name

end

def hello

puts "Hello,#{@name}!"

end

def method_missing(_)

hello

end

end

user = User.new('Gregory')

12種方法

1、最常用的方法

user.hello

關於這個方法沒什麼好說的,相信大量程式語言調用方法時都採用了這種方式。有意思的是,即使在點前後加上空格:user . hello,這個調用也依然有效。

2、省略括號

user.hello

嚴格來說,這種方式與前一種相同,只不過省略了括號,在 Ruby 中這個括號是可選的(只要代碼沒有歧義不寫也沒問題;但是當代碼可以用多種方式解釋時,就必須加上括號)。

3-4、使用 send和 public_send

user.send(:hello)

user.public_send(:hello)

在這兩種方式中,我將調用的方法名作為參數傳遞給 send 和 public_send(每個類都定義了send 和 public_send)。send 和 public_send 之間的區別在於,後者面向的是私有方法。如果在調用私有方法的時候報錯,那麼依然可以通過 send 調用。

在傳遞方法名的時候我使用了符號類型:(:hello),但是你也可以使用字符串:("hello")。

5-7、使用 「method」 和 「call」

user.method(:hello).call

user.method(:hello).

user.method(:hello)

在這三個例子中,後兩個只是語法糖,所以我把它們放在了一起。這種方式非常有意思。調用 user.method(:hello).call 會返回 Method 類的實例。這個對象可以作為值隨意傳遞,而且也可以隨時調用,它還存儲了其所屬對象的引用,因此,如果修改用戶名,那麼調用時就會使用新的用戶名:


method = user.method(:hello)
user.set_instance_variable(:@name, "Not Only Code")
method.call # prints "Hello, Not Only Code!"

這裡的 .和 等價於.call,而且還可以接受參數。proc.call(1,2,3)、 proc.(1,2,3)和 proc[1,2,3]的效果完全相同(儘管最後一個不支持命名參數)。

8、使用 「tap」

user.tap(&:hello)

tap 是一個非常有趣的小方法,它會接受一個塊,然後將自身作為參數傳遞進去並執行該塊,最終返回自身。我很少使用它,但是在某些情況下還是很有用的(例如連結方法時的副作用)。

語法 &:hello會將 :hello符號轉換為 Proc實例。更多信息請參閱(https://www.honeybadger.io/blog/how-ruby-ampersand-colon-works/)。Proc是一個可調用對象,就像前面示例中的 Method一樣。

9、在函數名上使用"to_proc"

:hello.to_proc.call(user)

我喜歡這種方式,因為這種調用反轉了順序:user 變成了函數的參數。實際上這種方式與上一個非常相似:Proc 的 call 函數將初始符號傳遞給接收到的參數。類似於如下代碼:

class Proc

def call(obj)

obj.send(@symbol_used_to_create_proc)

end

end

10、使用 「method_missing」

class User

def method_missing(_)

hello

end

end

user.i_am_a_lizard_king # prints "Hello, Gregory!"

user.i_can_do_everything # prints "Hello, Gregory!"

這種方式有點牽強,其實我使用的仍然是標準的方法,但我認為值得在此一提。

method_missing 方法會在對象收到未定義方法的調用時執行。它是一個非常強大的方法,是保障 Ruby 靈活性的基礎之一,但是它也有可能引發很多不易被察覺的bug(以及一些性能問題),因此請謹慎使用。

11、使用 「eval」

eval("user.hello")

這種方式也有點牽強,因為我使用的仍然是標準的調用語法,但是它的工作原理有很大不同。eval 將該字符串傳遞給 Ruby 的解析器和解釋器,就好像是我寫的代碼的一部分,然後執行該代碼。在代碼中千萬不要使用這種寫法,尤其是在允許用戶將某些值傳遞給應用程式的情況下。

12、使用 "source" 和 "instance_eval"

require 'method_source' # external gem

method_source = user.method(:hello).source

method_body =method_source.split("\n")[1...-1].join(";")

user.instance_eval(method_source)

這是最後一個,稍微有點放飛自我,所以解釋也有點長。這種方式需要依賴一個外部的包 method_source, 但這只是因為不用這個包的話,我就需要花費大量時間來編寫這些代碼(但都是 Ruby 代碼,不需要藉助魔法!)。下面我來解釋一下其中的工作原理:

user.method(:hello).source 將以字符串的形式返回方法的原始碼。其輸出是整段代碼(包括空格):

def hello

puts "Hello,#{@name}!"

end

method_source 包是如何實現的?Ruby中的 Method 類擁有一個 source_location函數,該函數可以返回方法原始碼的位置:文件以及方法開始處的行號。接下來,method_source 會打開這個文件,找到相應的行,找到 end (代表方法的結束),然後返回開頭與結束之間的代碼。

現在,我擁有了方法的完整代碼,接下來我需要刪除方法的定義和 end。在上述示例中,我只需要刪除第一行和最後一行,但如果方法只有一行,那麼就需要一些改動。第二行的輸出是一個字符串,值為:puts"Hello, #{@name}!"。

最後,我將這個字符串傳遞給 user 對象的 instance_eval。instance_eval 的工作原理類似於 eval,但它執行代碼的作用域不同。如果我調用 eval,則它將在整個文件的作用域上執行代碼,但其中不包含@name 變量的定義。將其傳遞給 instance_eval,可以確保它使用正確的值。

還有其它方法嗎?

以上只是一個有趣的小實驗。我相信 Ruby 還有很多調用方法的方式,因為這是一款強大又非常靈活的語言。你知道其它方法嗎?請在下面留言!

原文:https://www.notonlycode.org/12-ways-to-call-a-method-in-ruby/

本文為 CSDN 翻譯,轉載請註明來源出處。

關鍵字: