作為一個Ruby開發(fā)者雪营,讓人又愛又恨的便是元編程了蔚晨。
【前言】元編程是什么
簡單地說盟戏,元編程就是對語言本身的進行操作的一種編程手段板祝,最常見的就是代碼生成代碼
宫静。對于Ruby這門語言而言,不會元編程券时,等于不會這門語言孤里,因為這是它的核心能力與魅力。本文是基于閱讀《Ruby元編程》后記錄的一些自己的理解和看法橘洞。
元編程示例【示例1】
module Kernel
def attr_access(*args)
args.each do |arg|
define_method(arg) do
instance_variable_get("@#{arg}")
end
define_method("#{arg}=") do |val|
instance_variable_set("@#{arg}", val)
end
end
end
end
class Student
attr_access :name, :age
end
stu = Student.new
stu.age = 20
stu.name = 'Rapheal'
p stu.inspect
【示例1】
一個典型元編程的例子捌袜,它實現(xiàn)了Ruby中自帶的attr_accessor
相同的功能,作用是動態(tài)的為傳入的參數(shù)(上面代碼中是:name
和:age
)添加setter
和getter
方法(stu.age=xxx為其setter
方法, stu.age為其getter
方法)炸枣。這樣的方法避免了類似Java中的長篇setter
和getter
定義虏等。
【主題】對象模型
Ruby作為一種完全面向?qū)ο蟮木幊陶Z言,即使是一個數(shù)字抛虏、類博其、甚至一個方法都是一個對象套才。所謂對象迂猴,就是能對它進行一系列操作的一個集合。
打開類
對象模型篇第一講就是打開類
背伴。在【示例1】
代碼中其實就已經(jīng)包含了打開類
的一種具體實現(xiàn)方法沸毁。打開類
峰髓,即打開一個已經(jīng)存在的類或?qū)ο螅瑸槠?code>添加新方法息尺、修改已存在的方法
或刪除不需要的方法
的一種技術(shù)携兵。在【示例1】
中,Kernel
是Ruby庫中已經(jīng)存在的一個模塊搂誉,使用module Kernel
將其重新打開徐紧,并添加了一個新方法attr_access
。于是Kernel
模塊便在原來的基礎(chǔ)上新增了一個方法attr_access
炭懊。
修改一個已經(jīng)存在的方法【示例2】
str = "abc"
p str.to_s # 這里會輸出"abc"
class String
def to_s
"Nothing"
end
end
str = "abc"
p str.to_s # 這里輸出的就是"Nothing"了
String
也是Ruby庫自帶的類并级,to_s
是String
類已存在的方法,當(dāng)重新打開它并重寫了to_s
方法之后侮腹,原來方法的作用便不復(fù)存在了嘲碧,取而代之的是新方法的作用。(這種修改已經(jīng)存在的方法又被稱為猴子補丁
)
打開類的利與弊
通過【示例1】
與【示例2】
的代碼可以知道父阻,打開類技術(shù)可以很好的對已經(jīng)存在的類或方法進行修改愈涩,使之更符合個人的使用需求。然而加矛,若不加以思考隨意使用履婉,帶來的問題也是很嚴重的。比如String
類的to_s
方法斟览,作用就是要返回本身這個字符串谐鼎,結(jié)果被別人修改了這個定義,導(dǎo)致了所有引用這個方法的代碼全部失去了它本來的功能與意義趣惠。因此在使用打開類定義一個方法時狸棍,需要謹慎,盡量取一個當(dāng)前不存在的方法名來新定義一個方法
味悄。
對象中有什么
首先草戈,實例變量
,如【示例1】
中的:name
和:age
侍瑟,當(dāng)調(diào)用stu.name = 'rapheal'
之后唐片,stu
對象便產(chǎn)生了一個實例變量@name
。實例變量必須是以【一個@符號】開頭的變量名
涨颜。這時可以通過調(diào)用stu.instance_variables
來查看已經(jīng)存在的實例變量费韭,可以看到輸出中有:@name
這一條。
其次庭瑰,方法
星持。通過stu.methods
可以查看stu
對象能調(diào)用的所有方法。Ruby對象共享方法弹灭,但不共享實例變量督暂,共享的方法被稱為【實例方法】揪垄。【實例方法】定義在對象的類中逻翁,這樣可以使得同一類對象可以調(diào)用相同的方法饥努。
類也是對象。類對象所屬的類是Class類八回。類的方法即為Class類中定義的【實例方法】酷愧。
比如,所有類都有一個方法new
,而new
方法的定義就在Class
類中缠诅。我們甚至可以簡單的認為:ClassA = Class.new
和class ClassA; end
是等價的伟墙。它們都是在定義一個新的類ClassA
。
方法查找
提到方法查找滴铅,那么首先要知道的就是祖先鏈
戳葵。祖先鏈
其實就是記錄的一個類的繼承關(guān)系
的一個列表,可以通過調(diào)用ancestors
方法來查看汉匙。比如String.ancestors
返回的是[String, Comparable, Object, Kernel, BasicObject]
拱烁,于是我們可以判斷,String
類繼承自Object
噩翠,(Comparable
和Kernel
是兩個module
,它被包含在了其中的某個類中戏自,也會出現(xiàn)在祖先鏈中來,此處我們不討論祖先鏈中的module
)伤锚,Object
又繼承自BasicObject
擅笔。
理解了方法鏈,再回頭來看方法查找屯援。Ruby中的方法查找有個原則叫作向右猛们,再向上。
比如狞洋,有一個String
類的對象str
弯淘,調(diào)用方法str.test_call_method
,這時Ruby解釋器會:
- 1吉懊、
【向右】
來到str
所屬的String
類查看String
類是否定義了test_call_method
這個方法庐橙,若定義了則直接調(diào)用 - 2、
【向上】
否則查看Comparable
這個module
中是否定義這個方法(因為祖先鏈中有這個module
借嗽,并且排在了第二個态鳖,即String
類和Object
類中間) - 3、
【向上】
若還未定義恶导,則來到父類Object
類查找 - 4浆竭、重復(fù)上述2、3步驟直到
BasicObject
類
上述步驟中,步驟1
稱為向右
兆蕉,步驟2羽戒、3
稱為向上
缤沦。整個流程中虎韵,可以看出,方法查找是優(yōu)先向右(所屬類)
查找缸废,再向上(優(yōu)先是自身包含的模塊然后是父類)
查找包蓝。因此稱為向右,再向上
原則企量。
對于類所包含的模塊會在方法查找時定義為一個匿名類
并插入到祖先鏈中該類的直接上方
测萎。
關(guān)于self
在某個特定時刻,一定會有一個指定的對象在執(zhí)行届巩,這個對象就是self
對象硅瞧。最開始接觸這個的時候,會有一個誤區(qū)認為self
是當(dāng)前調(diào)用方法的執(zhí)行者
恕汇,然而事實上self
是當(dāng)前方法執(zhí)行的接收者
腕唧。簡單說即是,當(dāng)前方法調(diào)用的結(jié)果會傳遞返回給這個self對象瘾英。
談到self
枣接,那么就應(yīng)該順便說一下private
。Ruby中的private是和self
相關(guān)的缺谴,在Ruby類的定義的private
方法是不能被顯式調(diào)用的但惶。
private示例【示例3】
class A
def print_self
self.t_pri
end
def print_self_2
t_pri
end
private
def t_pri
p "hello world"
end
end
obj1 = A.new
obj1.print_self_2 # 輸出 "hello world"
obj1.print_self # 報錯, NoMethodError: private method 't_pri' called
obj1.t_pri # 報錯,同上
從【示例3】
湿蛔,可以看出私有方法t_pri
只能由self
隱式調(diào)用膀曾,即私有方法只能在定義的內(nèi)部以直接調(diào)用方式調(diào)用,而不能在任何地方以 xxx.yyyy的方式調(diào)用阳啥。同時妓肢,若沒有顯式指定方法接收者,那么調(diào)用方法的接收都將隱式指定為self對象苫纤。