我眼中的元編程-方法篇

Ruby是一門動(dòng)態(tài)語(yǔ)言忘嫉,動(dòng)態(tài)創(chuàng)建與調(diào)用方法是其中一個(gè)體現(xiàn)盖奈。

動(dòng)態(tài)方法

動(dòng)態(tài)調(diào)用方法(動(dòng)態(tài)派發(fā))

動(dòng)態(tài)調(diào)用方法拆又,是指在代碼中不通過(guò)硬編碼而是在程序運(yùn)行時(shí)自動(dòng)去決定要調(diào)用的方法的一種行為养晋。

示例代碼1
class Student
   attr_accessor :name, :age, :birthday
   def initialize(args = {})
     name = args[:name]
     age    = args[:age]
     birthday = args[:birthday]
   end
end

【示例代碼1】initialize方法中偏窝,給三個(gè)字段賦值的方式就是一種典型的硬編碼方式收恢,假如這三個(gè)字段的名稱有改動(dòng),抑或添加祭往、去掉字段的時(shí)候伦意,不得不同時(shí)修改這個(gè)方法。為了避免這種情況硼补,這里可以考慮使用動(dòng)態(tài)調(diào)用的方式來(lái)重構(gòu)它驮肉。

示例代碼2
class Student
  attr_accessor :name, :age, :birthday
  def initialize(args = {}) 
    args.each do |key, value|
       method_name = "#{key}="
       self.send("#{key}=", value) if self.respond_to?(method_name)
    end
  end
end

通過(guò)【示例代碼2】的重構(gòu),但凡attr_accessor后面的字段有變動(dòng)時(shí)括勺,initialize方法都會(huì)自動(dòng)進(jìn)行適配缆八。那么實(shí)現(xiàn)的原理是什么呢曲掰?

在Ruby中,方法調(diào)用其實(shí)是向一個(gè)對(duì)象發(fā)送了一條消息奈辰,當(dāng)接收方接收消息后栏妖,會(huì)在對(duì)象的祖先鏈中去尋找這個(gè)方法,找到之后調(diào)用它并返回給self對(duì)象(詳細(xì)見(jiàn)【對(duì)象模型篇】)奖恰。也就是說(shuō)吊趾,當(dāng)調(diào)用str.method的時(shí)候,本質(zhì)上就是發(fā)送了一條方法調(diào)用的消息瑟啃,接收者是str對(duì)象论泛,它等價(jià)于str.send(:method)。因此示例代碼便很好理解了蛹屿,它是將args這個(gè)hash中的值進(jìn)行遍歷屁奏,動(dòng)態(tài)調(diào)用attr_accessor生成的settergetter方法。但有一個(gè)問(wèn)題错负,如果參數(shù)中有在attr_accessor未定義的字段怎么辦坟瓢?比如Student.new({ year: 2016 })year字段是未在attr_accessor中定義的犹撒,如果調(diào)用self.year =這個(gè)方法折联,是會(huì)拋異常的。所以這里添加了respond_to?來(lái)判斷這個(gè)方法是否是存在的识颊,存在再對(duì)它進(jìn)行調(diào)用賦值诚镰。

動(dòng)態(tài)定義方法

關(guān)于動(dòng)態(tài)定義方法,其實(shí)在第一章對(duì)象模型篇【示例代碼1】已經(jīng)在使用了祥款,就是對(duì)define_method的使用清笨。在此基礎(chǔ)之上,此處實(shí)現(xiàn)一個(gè)更加具有可用性的案例:

示例代碼3
module Kernel
   def attr_access(*args)
      args.each do |arg|
         define_method(arg) do
            instance_variable_get("@#{arg}")
         end
         define_method("#{arg}=") do |value|
            instance_variable_set("@{arg}=", value)
         end
      end
   end

  def cattr_access(*args)
     args.each do |arg|
        define_singleton_method(arg) do 
           self.class_variable_get("@@#{arg}")
        end
        define_singleton_method("#{arg}=") do |value|
           self.class_variable_set("@@#{arg}", value)
        end
     end
  end
end

class A
  cattr_access :a
end
A.a = 1
p A.a  # 輸出1

此處不再說(shuō)明define_methodattr_access的使用镰踏,重點(diǎn)說(shuō)明一下define_singleton_methodcattr_access的實(shí)現(xiàn)函筋。

define_singleton_methoddefine_method的區(qū)別是,前者定義的是單例方法(這里可稱為類方法)奠伪,后者定義的是實(shí)例方法跌帐。從用法來(lái)看,cattr_access聲明的變量直接在類(這里是A)上調(diào)用绊率,而attr_access聲明的變量需要在A類對(duì)象實(shí)例化(A.new)之后調(diào)用谨敛。同理,class_variable_setclass_variable_get定義的是單例變量(這里指類變量)滤否,而instance_variable_setinstance_variable_get定義的是實(shí)例變量脸狸。由于Ruby的語(yǔ)法約定,以@開(kāi)頭的為實(shí)例變量,以@@開(kāi)頭的為類變量炊甲,因此泥彤,在定義變量時(shí)尤其要注意變量的全名,否則會(huì)拋異常卿啡。

幽靈方法

還記得之前在方法查找中吟吝,如果找不到方法時(shí),會(huì)觸發(fā)一個(gè)NoMethodError的異常拋出颈娜。然而它來(lái)源于向?qū)ο蟀l(fā)送了一個(gè)消息調(diào)用了一個(gè)方法叫做method_missing剑逃。

假如對(duì)一個(gè)String類對(duì)象str調(diào)用test_method_a,即str.test_method_a官辽,由于這個(gè)方法未定義蛹磺,因此在祖先鏈中找不到這個(gè)方法。此時(shí)會(huì)發(fā)送一個(gè)消息str.send(:method_missing, :test_method_a)同仆,從而拋出NoMethodError的異常萤捆。也就是說(shuō),當(dāng)找不到要調(diào)用的方法時(shí)乓梨,會(huì)自動(dòng)觸發(fā)調(diào)用method_missing方法鳖轰。那么如果重寫(xiě)了某個(gè)類的method_missing方法會(huì)是什么樣的結(jié)果呢清酥?

示例代碼4
class XmlGen
  def method_missing(name, *args, &block)
     if %W(html head title body).include?(name.to_s)
        define_singleton_method(name) do |arg = nil, &blk|
           str  = "<#{name}>"
           str += arg if arg
           str += blk.call if blk
           str += "</#{name}>"
           str
        end
        self.send(name, *args, &block)
     end
  end
end

xml = XmlGen.new
str = xml.html do 
   xml.head do
      xml.title "Test"
   end
end
p str  # 輸出<html><head><title>Test</title></head></html>

由于在method_missing中對(duì)調(diào)用方法的名字做了限制扶镀,必須是htmlhead焰轻、title臭觉、body其中之一才會(huì)生成代碼,因此無(wú)需擔(dān)心其它額外正常調(diào)用不存在方法的時(shí)候不能正常拋出NoMethodError異常的情況辱志。由于在調(diào)用不存在的方法時(shí)就會(huì)調(diào)用method_missing這個(gè)方法蝠筑,因此如果要重寫(xiě)這個(gè)方法一定要格外小心,能力越大揩懒,責(zé)任越大什乙。

幽靈方法與普通動(dòng)態(tài)方法的優(yōu)劣

普通動(dòng)態(tài)方法是指,在類初始化時(shí)便使用define_method等手段將需要的所有方法定義好已球。幽靈方法本質(zhì)是在調(diào)用時(shí)臣镣,如果發(fā)現(xiàn)不存在方法時(shí),那么即時(shí)定義這個(gè)方法并產(chǎn)生一次調(diào)用智亮,從示例可以看出幽靈方法在定義方法時(shí)也是調(diào)用的define_method等行為來(lái)定義動(dòng)態(tài)方法忆某。與普通定義動(dòng)態(tài)方法的區(qū)別是,如果一個(gè)對(duì)象永遠(yuǎn)沒(méi)有調(diào)用一個(gè)方法阔蛉,那么這個(gè)方法永遠(yuǎn)不會(huì)被定義弃舒,只有調(diào)用過(guò)一次時(shí)它才會(huì)被定義,因此使用幽靈方法時(shí)状原,對(duì)象所占用的內(nèi)存空間比普通動(dòng)態(tài)方法要少聋呢,反之付出的代價(jià)是第一次在祖先鏈中查找該方法的時(shí)間變長(zhǎng)苗踪。這可以認(rèn)為是一種以時(shí)間換取空間的策略。

動(dòng)態(tài)代理

動(dòng)態(tài)代理的原理是削锰,對(duì)a對(duì)象的操作轉(zhuǎn)移到b對(duì)象上來(lái)徒探,Ruby中使用delegate庫(kù)來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理。

示例代碼5
class UserProfile
    def initialize(name)
       @name = name
    end
    def hello
       "#{@name} says hello."
    end
end

class User < DelegateClass(UserProfile)
   def initialize(user_profile)
      super(user_profile)
   end
end
user_profile = UserProfile.new("Rapheal")
user = User.new(user_profile)
p user.hello  # 輸出 "Rapheal says hello."
關(guān)于respond_to?

respond_to?是Ruby中用于判斷一個(gè)方法是否存在的一個(gè)方法喂窟。比如测暗,Class.respond_to?(:new) #返回true,說(shuō)明Class這個(gè)類可以調(diào)用new方法磨澡。這個(gè)方法通常與define_method碗啄、method_missing等方法一起使用,與method_missing一樣稳摄,不到萬(wàn)不得已稚字,不要修改這個(gè)方法。

本章主要講了使用define_method來(lái)定義動(dòng)態(tài)方法厦酬,使用method_missing來(lái)處理NoMethodError的情況胆描。依然是那句話,能力越大仗阅,責(zé)任越大昌讲。如果能加以善用,那么這些特征能使的代碼的靈活度越來(lái)越高减噪,反之只能使之晦澀難懂甚至導(dǎo)致難以追蹤的BUG短绸。慎之!慎之筹裕!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末醋闭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子朝卒,更是在濱河造成了極大的恐慌证逻,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抗斤,死亡現(xiàn)場(chǎng)離奇詭異囚企,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)豪治,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門洞拨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人负拟,你說(shuō)我怎么就攤上這事烦衣。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵花吟,是天一觀的道長(zhǎng)秸歧。 經(jīng)常有香客問(wèn)我,道長(zhǎng)衅澈,這世上最難降的妖魔是什么键菱? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮今布,結(jié)果婚禮上经备,老公的妹妹穿的比我還像新娘。我一直安慰自己部默,他們只是感情好侵蒙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著傅蹂,像睡著了一般纷闺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上份蝴,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天犁功,我揣著相機(jī)與錄音,去河邊找鬼婚夫。 笑死浸卦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的请敦。 我是一名探鬼主播镐躲,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼侍筛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起撒穷,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤匣椰,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后端礼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體禽笑,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年蛤奥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了佳镜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凡桥,死狀恐怖蟀伸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤啊掏,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布蠢络,位于F島的核電站,受9級(jí)特大地震影響迟蜜,放射性物質(zhì)發(fā)生泄漏刹孔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一娜睛、第九天 我趴在偏房一處隱蔽的房頂上張望髓霞。 院中可真熱鬧,春花似錦畦戒、人聲如沸酸茴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)薪捍。三九已至,卻和暖如春配喳,著一層夾襖步出監(jiān)牢的瞬間酪穿,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工晴裹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留被济,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓涧团,卻偏偏與公主長(zhǎng)得像只磷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泌绣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理钮追,服務(wù)發(fā)現(xiàn),斷路器阿迈,智...
    卡卡羅2017閱讀 134,601評(píng)論 18 139
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉元媚,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評(píng)論 0 9
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,520評(píng)論 25 707
  • 傍晚的天空,陰沉沉的苗沧,好似要下雨刊棕。但,還是固執(zhí)地?fù)Q好運(yùn)動(dòng)裝出門了待逞,去跑步甥角。心中就一個(gè)信念,跑步去识樱。終于嗤无,戰(zhàn)勝了懶惰...
    烏鴉一只閱讀 256評(píng)論 0 0
  • 為什么別人手機(jī)里放出來(lái)的歌震束,有時(shí)候就是覺(jué)得好聽(tīng)呢?Beyond的《海闊天空》翁巍,在吃早餐的地方聽(tīng)到的驴一,覺(jué)得特別美好。...
    安擇閱讀 265評(píng)論 0 2