ruby 狀態轉移

來源:互聯網
上載者:User
0. 引言      昨天遇到一個問題,就是關於對象狀態轉移的問題,我姑且這樣命名吧。簡要描述一下就是:對於一個人,他有進食,協助他人,戀愛等功能,但是這些功能是有先後順序的,對於剛出生的人,他要先學會進食,然後隨著他的成長,他逐漸學會協助他人,在這個過程中他學會了愛與被愛,當他遇到一個合適的女孩,他就墜入了愛河。整個過程反映到程式上就是,必須按照下面的順序調用方法:
man=Human.newman.feedman.fall_in_love   # Errorman.help_peopleman.fall_in_love

     如果你調用某個功能時沒有完成前面的事情,就像上面的例子這樣,一個人尚未學會協助他人的人,我們是不希望他去戀愛的,這樣一個不懂得互助互愛的人怎麼可能珍惜自己的愛人呢?     所以,對象狀態轉移就是:某個對象隨著狀態轉移獲得調用新方法的能力或許可權,未達到某個狀態前無法調用該狀態下的方法。1. 目標      仔細想想,其實這類問題出現還是比較普遍的,比如一個瀏覽器處理類,它必須要在登陸操作後才允許執行修改個人資訊。所以,有必要為了這類問題思考一個解決方案。那麼,首先要明確的是,我想要怎樣實現這樣一個功能。為每個類去實現一個這樣的狀態轉移顯然不是ruby way。所以,我覺得對於我自己,我希望這樣處理一下我的Human類之後,我就能像引言中那樣直接使用狀態轉移提供的功能:

class Humaninclude Statedef feedputs"feed myself"enddef protect_envputs "protect environment"enddef help_peopleputs "help other people"enddef fall_in_loveputs "love someone"enddefine_chain :feed,[:protect_env,:help_people],:fall_in_loveend

      如代碼所示,我希望在我使用的類中包含一個State模組,然後用define_chain定義一個方法鏈,那麼方法鏈中的方法,必須要在前一個方法調用過之後才可以被調用,否則就會拋出異常。另外,在定義方法鏈的define_chain中,我希望可以包含列表,列表中的方法需要至少被調用一種才能執行方法鏈的後續調用。

      好吧,這樣看起來,似乎是像模像樣的ruby解決方案了,那麼,下面就看看如何來實現這個State模組。2. 環繞別名     首先,我們肯定需要在define_chain方法上做文章。該方法實際完成狀態轉移方法鏈的定義,那麼問題的關鍵是:我知道了這一串方法,怎麼樣保證在調用下一個方法前,明確上一個方法是否被調用了呢?很顯然,我們需要一個變數來儲存狀態,在每次調用方法前檢查是否能夠調用當前方法,如果能夠,則在調用完成之後更新狀態。那麼怎麼做呢?總不能要求編寫Human類的程式員在每個方法調用前先檢查一下狀態,在調用完成後再更新狀態吧,那顯然是會被鄙視的。實際上,作為一個ruby程式員,每個人都需要會一點點魔法,這次的魔法就是環繞別名。    假如,對於某個方法名method,我們可以這樣環繞起來:
define_method "#{method}_in_chain" do |*params,&block|validate_state_for method.to_symself.send "#{method}_out_chain",*params,&blockupdate_state_for method.to_symendalias_method "#{method}_out_chain",methodalias_method method,"#{method}_in_chain"

     這部分代碼就是define_chain方法的主體,這樣,在定義了狀態轉移方法鏈之後,直接調用在方法鏈中的方法,就會自動使用validate_state_for方法檢查方法是否可以被調用,在完成調用後使用update_state_for方法更新狀態。

   然後我們去實現validate_state_for和update_state_for方法,這兩個方法實現很簡單,後面再說,我們的State模組看起來基本是這樣的:
module Statedef define_chain(*args)enddef validate_state_for(method)enddef update_state_for(method)endend

    好吧,問題的最關鍵區段解決了,但還是有一些細節,不要小看細節,它決定成敗。3. 類擴充混入   顯然,我們的define_chain方法必須作為類方法存在,這很簡單,可以使用擴充混入。即

class Human  extend Stateend

   但問題來了,我只希望define_chain被作為類方法混入,而validate_state_for和update_state_for方法仍然需要作為類執行個體方法。那麼直接混入肯定就不行了,這時就需要使用ruby另一個魔法了——類擴充混入,將部分方法作為類方法混入,部分方法作為執行個體方法混入。這種魔法使用了included鉤子。

module Statedef self.included(base)base.extend StateMakerendmodule StateMakerdef define_chain(*args)endenddef validate_state_for(method)enddef update_state_for(method)endend

     現在,在使用下面的方法混入,就獲得了我想要的效果。我能夠用類方法define_chain定義狀態方法鏈,也能夠執行個體化Human對象調用它的validate_state_for執行個體方法。

class Human  include Stateend

    
4. 最後一步,實現     我們的State狀態轉移模組的結構就是這樣了,那麼下面就需要具體實現了。    狀態判斷邏輯非常簡單:按照狀態方法鏈的定義,從左至右從0開始編號,而對象狀態也從0開始,僅到目前狀態大於等於方法編號時,才允許調用該方法。     狀態更新邏輯:僅當狀態方法編號等於當前對象狀態時,才更新狀態,即將狀態值加1。     這就是State模組的執行個體方法實現:

module Statedef validate_state_for(method)raise "State is too low to execute #{method}" unless min_state_for(method) <= stateenddef min_state_for(method)self.class.state_chain.find_index{|k,v| v.include? method}enddef update_state_for(method)@_state_from_object_monitor_+=1 if min_state_for(method) == stateenddef reset_state@_state_from_object_monitor_=0enddef state@_state_from_object_monitor_=0 unless @_state_from_object_monitor_@_state_from_object_monitor_endend

    該模組還提供了reset_state方法重設狀態值。另外,min_state_for方法用於擷取調用某個方法的最低狀態值,該方法中實際上也使用了ruby一點點小魔法,類執行個體變數,state_chain是一個類方法,它擷取了是我們定義的狀態轉移方法鏈的一個hash表,該表是一個類執行個體變數,這個hash具體結構馬上就會看到。    下面就是State::StateMaker的的define_chain方法的實現:

module Statemodule StateMakerdef define_chain(*args)args.map{|x| x}args.flatten.each do |method|define_method "#{method}_in_chain" do |*params,&block|validate_state_for method.to_symself.send "#{method}_out_chain",*params,&blockupdate_state_for method.to_symnilendalias_method "#{method}_out_chain",methodalias_method method,"#{method}_in_chain"end@chain_methods=args.each_with_index.inject({}) do |memo,(v,index)|memo[index]=v.class==Symbol ? [v] : vmemoendnilenddef state_chain@chain_methodsendendend

      define_chain方法的前半部分使用環繞別名來包裹特定方法,後半部分就是產生方法鏈的hash表,產生的hash表被儲存在執行個體變數@chain_methods中,由於define_chain被作為類方法混入,所以它自然也成為了混入類的類執行個體變數,注意,盡量多使用類執行個體變數而不要使用類變數。而state_chain方法也同時混入成為類方法,該方法純粹就是用來擷取類執行個體變數chain_methods的。如1.目標中的方法鏈產生的hash表的結構是:

{0=>[:feed], 1=>[:protect_env, :help_people], 2=>[:fall_in_love]}

5. 結尾       現在,整個狀態轉移方法調用就完成了,可以像引言中那樣去使用了。不過,這僅僅是個開始,ruby的原則就是DRY,還有細節的地方需要完善修改,比如用ruby2.0就可以更漂亮地完成環繞別名等等。6. 附錄     下面是State模組完整代碼,供參考。

module Statedef self.included(base)base.extend StateMakerendmodule StateMakerdef define_chain(*args)args.map{|x| x}args.flatten.each do |method|define_method "#{method}_in_chain" do |*params,&block|validate_state_for method.to_symresult=self.send "#{method}_out_chain",*params,&blockupdate_state_for method.to_symresultendalias_method "#{method}_out_chain",methodalias_method method,"#{method}_in_chain"end@chain_methods=args.each_with_index.inject({}) do |memo,(v,index)|memo[index]=v.class==Symbol ? [v] : vmemoendnilenddef state_chain@chain_methodsendenddef validate_state_for(method)raise "State is too low to execute #{method}" unless min_state_for(method) <= stateenddef min_state_for(method)self.class.state_chain.find_index{|k,v| v.include? method}enddef update_state_for(method)@_state_from_object_monitor_+=1 if min_state_for(method) == stateenddef reset_state@_state_from_object_monitor_=0enddef state@_state_from_object_monitor_=0 unless @_state_from_object_monitor_@_state_from_object_monitor_endend
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.