Day13 - Ruby比一比: instance_eval 和 class_eval方法

第13天!昨天談到了class variable,class instance variable和instance variable,也發如今實務上,類別實體變數和實體變數纔是主流。今天咱們要多談兩個跟前一篇的變數有關的方法:instance_eval和class_eval。讓天天都主題都環環相扣!面試

 

Ruby經典面試題目#13ide

instance_eval和class_eval的差異?What's the difference between instance_eval and class_eval?ui

 

instance_evalgoogle

昨天文章提到一個重要概念:可以讀取變數的屬性是很是重要的,讓咱們能夠更方便的讀取名稱相同,但其實值不一樣的物件。eval表明着evaluation,有估值、取值的意涵。讓咱們把昨天的attr_accessor概念引入,立刻來寫程序碼實驗instance_eval:lua

 

[instance_eval案例A:attr_accessor]url

由過去幾天的寫做經驗,我發現一篇文章的開頭最難下筆、也是最重要的,舉例能讓本身懂(還有讓個人讀者、觀衆、加油羣啦啦隊懂)更不是容易的事,因此我習慣從自身生活經驗出發,把寫程序變成像寫日記同樣有趣、貼近生活。:)idea

 

話說最近令我期待的事情是,再過10天就要跑馬拉松了!所以我打算創建RunMarathon類別,new出兩個物件hm半程馬拉松和fm全程馬拉松,並各自指定對應的km千米數值:code

 

class RunMarathonget

attr_accessor:kmstring

end

 

hm = RunMarathon.new

hm.km = 21

 

fm = RunMarathon.new

fm.km = 42

 

p hm # => #<RunMarathon:0x000055f60ed4f0d0 @km=21>

p fm # => #<RunMarathon:0x000055f60ed4f0a8 @km=42>

 

p hm.km

p fm.km

p hm.instance_eval { @km } # 21和hm.km的結果相同

p fm.instance_eval { @km } # 42和fm.km的結果相同

 

p RunMarathon.instance_methods(false)#[:km=,:km]

這裏用到兩個instance_methods實體方法km=(寫入值)和km(讀出值)。

 

若是咱們用.instance_eval方法取值,結果顯示:

 

#<RunMarathon:0x000055f60ed4f0d0 @km=21>

#<RunMarathon:0x000055f60ed4f0a8 @km=42>

21

42

21

42

[:km=,:km]

很好!成功用instance_eval印出值了!

 

[instance_eval案例B:只用initialize()方法]

還記得第一天提到的初始化方法initialize,創建實體變數。

 

咱們能夠將代碼改寫爲在RunMarathon類別加入initialize()方法,讓咱們在new出物件的同時傳入千米數,代碼變成以下:

 

class RunMarathon

def initialize(km)

@km = km

end

 

def km

@km

end

end

 

hm = RunMarathon.new(21)

fm = RunMarathon.new(42)

 

p hm

p fm

 

p hm.km

p fm.km

p hm.instance_eval { @km } # 21和hm.km的結果相同

p fm.instance_eval { @km } # 42和fm.km的結果相同

 

p RunMarathon.instance_methods(false)#[:km]

咱們使用了.instance_methods確認目前用到哪些實體方法:

 

#<RunMarathon:0x000055c2a0e3eac8 @km=21>

#<RunMarathon:0x000055c2a0e3eaa0 @km=42>

21

42

[:km]

以上顯示,以前使用到的一對實體方法(讀日記、與寫日記xyyfl),只剩下讀取:km。km=(寫入值)已經不見了!

 

仔細思考一下,緣由是咱們將案例A的RunMarathon類別中attr_accessor:km這段代碼,以.initialize()方法取代。方法變了,變數的傳值方式也會不一樣。如下的這段:

 

hm = RunMarathon.new

hm.km = 21

 

fm = RunMarathon.new

fm.km = 42

被改寫成:

 

hm = RunMarathon.new(21)

fm = RunMarathon.new(42)

以上觀念是把昨天+今天的一塊兒整合複習。

 

[instance_eval案例C:只用initialize()方法,但將def km方法刪除]

若是,咱們把RunMarathonclass的定義千米變數方法:

 

def km

@km

end

移除,會發生什麼事呢?

 

(我想你應該猜到了,會影響到hm.km和fm.km,用到km的這兩行代碼:)

 

class RunMarathon

def initialize(km)

@km = km

end

end

 

hm = RunMarathon.new(21)

fm = RunMarathon.new(42)

 

p hm

p fm

#p hm.km #undefined method `km'(NoMethodError)

#p fm.km #undefined method `km'(NoMethodError)

 

p hm.instance_eval { @km }

p fm.instance_eval { @km }

 

p RunMarathon.instance_methods(false)#[]

沒有方法了。hm.km和hm.fm找不到方法(NoMethodError)。

 

咱們用註釋#消去無用的這兩行。

 

然而.instance_eval如往常同樣堅守崗位幫咱們印出值。

 

此時.instance_methods的印出結果顯示出,此時咱們並無用到任何的實體方法。

 

#<RunMarathon:0x000055cb6e5142f0 @km=21>

#<RunMarathon:0x000055cb6e5142c8 @km=42>

21

42

[]

爲了更近一步瞭解,我去Ruby-doc.org查到這段話:

 

instance_eval evaluates a string containing Ruby source code,or the given block,within the context of the receiver(obj).In order to set the context,the variable self is set to obj while the code is executing,giving the code access to obj's instance variables and private methods.出處

 

我發現instance_eval用來定義於任何的object(包含class,由於類別也是一種物件),還能夠存取到私有方法private method!立馬來寫code研究一下。

 

[instance_eval案例D:存取private method]

話說在我心深處藏了一我的生願望:跑超級馬拉松(ultramarathon,千米數超過50以上的馬拉松),所以我決定把這個心裏祕密放在private method裏:

 

class RunMarathon

def initialize(km)

@km = km

end

 

private

def my_resolution

「I'm going to run ultrathon #{@km} in the future!」

end

end

 

um = RunMarathon.new(100)

p um

p um.instance_eval { @km }

p um.instance_eval { my_resolution }

結果顯示爲:

 

#<RunMarathon:0x0000564cf8966b58 @km=100>

100

「I'm going to run ultrathon 100 in the future!」

利用.instance_eval{private method}探尋心裏深處,好熱血的人生宣言啊~

 

class_eval

若是咱們想要提取值不少次,又不想一直重複寫這樣的代碼:

 

p hm.instance_eval { @km } #告訴我半馬千米數!

p fm.instance_eval { @km } #告訴我全馬千米數!

p um.instance_eval { @km } #告訴我超馬千米數!

 

只要看到代碼有重複的部分,咱們就能夠思考,如何將重複概念提高到class類別的層次,變成class_eval:

 

class RunMarathon

def initialize(km)

@km = km

end

end

 

RunMarathon.class_eval do #放RunMarathon類別的外面!定義新的類別方法

def km

@km #這個是類別實體變數唷!

end

end

 

hm = RunMarathon.new(21)

fm = RunMarathon.new(42)

 

p hm

p fm

p hm.km #21與hm.instance_eval {@km}值相同

p fm.km #42與fm.instance_eval {@km}值相同

 


p RunMarathon.instance_methods(false)#[:km]

結果以下:

 

#<RunMarathon:0x00005619eeb8ec88 @km=21>

#<RunMarathon:0x00005619eeb8ec60 @km=42>

21

42

[:km]

瞧!是否是跟[instance_eval案例B:只用initialize()方法]這裏所舉的例子一。模。一。樣!

 

爲何

 

class RunMarathon

def initialize(km)

@km = km

end

end

 

RunMarathon.class_eval do #放外面!定義類別方法

def km

@km #這個是類別實體變數唷!

end

end

 

 

class RunMarathon

def initialize(km)

@km = km

end

 

def km

@km

end

end

會出現相同的結果呢?

 

我在史丹佛大學CS142課程這篇教材找到解答(npx133):

 

class_eval is equivalent to typing the code inside a class statement.

 

以更簡單的構架爲例好了:

 

MyClass.class_eval do

def num

@num

end

end

會等於

 

class MyClass

def num

@num

end

end

真是太神奇了!

 

總結

因此回到今天最一開頭的舉例[instance_eval案例A:案例B:案例C],透過移除部分的代碼作實驗,從instance_eval,串到class_eval,再串回到instance_eval,好像又回到初衷、豁然開朗的感受呢!

 

我也領悟到了,其實程序寫法均可以換來換去,重點是,你想實現的功能是什麼?不一樣的寫法之間又有什麼優缺點比較?像在這篇提到:class_eval概念,跟module_eval是相似的,拿來用做擴充rails gem所定義的class,這也許能夠當我第20天候的文章素材idea!

 

最後,來複習一下昨天的變數比一比!

 

類別實體變數class instance variable實體變數instance variable

@類別實體變數@實體變數

可用attr_accessor的方式改寫可用attr_accessor的方式改寫

用在類別方法,不可用在實體方法用在實體方法

恰好在今天的例子class_eval、instance_eval,昨天瞭解的:類別實體變數和實體變數都有派上用場:)

也許這就是一種「過去天天累積的努力,成就如今的本身」最佳的例子吧!

相關文章
相關標籤/搜索