回想起咱門初學C跟Java語言的時候,或許會以為這個世界上只有這兩門語言。 當時老師或者教科書肯定不是一上來就教你如何用這門語言去連接數據庫,而是要求你用這門語言去實現一些簡單的數據結構,以及排序這類簡單的算法。Oh,sorry,我可能讓你回憶起一些不太好的事情了。特別是后來背棄了編程這條路的同學,你們一定覺得這個過程特別的枯燥,而且艱辛。
還記得那時候我們為了實現一種叫做先進先出的被呼喚為隊列的數據結構,以及一種后進先出的被呼喚為棧的數據結構耗費了我們多少時間了嗎?現在的你肯定不會手動去寫這些數據結構了,別人早就寫好的代碼很容易就能夠在網上找到,我們直接用便可(當然那時候我們不知道有Github這種東西)。
后來,我決定要走一條不尋常的路(研習一門動態語言)的時候。我發現更不用做這些事情了,所謂的棧,隊列,這些數據結構,在許多語言的內置庫里面都已經有類似的實現。今天,請允許我用Ruby來講這個事情,另外我會結合Ruby的Forwardable
模塊,優雅地實現方法代理。
1. 簡單模擬棧跟隊列
Ruby的內置類 Array
的存在,為我們簡單操作序列型數據提供了可能,我們只需要用字面量的方式就能夠很容易地創建一個序列
array = [1,2,3,4]
=> [1, 2, 3, 4]
然后?我們可以基于Array
的實例來簡單地模擬棧或者隊列的行為。
1)模擬棧
我只需要簡單地使用Array#shift
以及Array#unshift
兩個實例方法就能夠很簡單地模擬棧的功能
# 棧的實現
pry(main)> array
=> [1, 2, 3, 4]
pry(main)> array.unshift(1)
=> [1, 1, 2, 3, 4]
pry(main)> array.shift()
=> [1]
pry(main)> array
=> [1, 2, 3, 4]
是不是很簡單?我只需要反復調用上面兩個方法,就能夠完成棧能完成的事情了。
2)模擬隊列
接下來模擬隊列,隊列也比較簡單,我們只需要調用Array#push
方法就可以在隊列的末尾插入一個元素。然后通過Array#shift
方法,移除并獲取隊列頭部的元素。
# 隊列的實現
pry(main)> array
=> [1, 2, 3, 4]
pry(main)> array.push(1)
=> [1, 2, 3, 4, 1]
pry(main)> array.shift()
=> [1]
pry(main)> array
=> [2, 3, 4, 1]
但是如果在平時的業務代碼中我們給隊友們提供這樣的隊列,或者棧的話。你可能就見不到明天的太陽了,我相信軟件行業里面爛代碼吸引的仇恨,并不亞于英雄聯盟里面的豬隊友。那我們一般會怎么做?請接著往下讀。
2. 定義隊列或者棧的相關類
更加語義化的方式是定義相關的類(Stack,Queue),然后封裝對應的操作方法。這樣,當別人用到我們定義好的有特定行為的類的時候心情就會更加舒坦。我們代碼可讀性也更高。我開篇一直在扯語言的事情,就是提醒一下在我們使用動態語言的時候,請盡量跳出靜態語言的思維定勢。很多事情其實可以更簡單。動態語言往往更關注行為,而不是類型。
1) 屌絲方案
class Queue
def initialize
@q = []
end
def enq(*argument_list)
@q.push(*argument_list)
end
def deq(*argument_list)
@q.shift(*argument_list)
end
end
我知道現在的代碼并不是很好看,但是請相信我,最起碼它是可以運行的
pry(main)> q = Queue.new
=> #<Thread::Queue:0x007ff00f0ccfe0 @q=[]>
pry(main)> q.enq(1,2,3,4)
=> [1, 2, 3, 4]
pry(main)> q
=> #<Thread::Queue:0x007ff00f0ccfe0 @q=[1, 2, 3, 4]>
pry(main)> q.deq()
=> 1
pry(main)> q
=> #<Thread::Queue:0x007ff00f0ccfe0 @q=[2, 3, 4]>
pry(main)> q.deq(2)
=> [2, 3]
pry(main)> q
=> #<Thread::Queue:0x007ff00f0ccfe0 @q=[4]>
雖然有點粗糙,但最起碼它也是一個隊列。它能夠完成隊列的基本行為。
2) 高富帥的寫法
初入Ruby難免會寫出一些比較矮窮搓的代碼,沒事,隨著我們經驗的累積,我相信我們會慢慢寫出高富帥的代碼出來。為了讓上述的代碼更簡短一些,我們可以增強原有類的功能。Ruby經常會使用Module
來完成增強任務。下面介紹一個叫做Forwardable
的模塊,它提供了一些好用的類方法,可以幫我們簡單地定義對應類的一些實例方法,并且,把定義好的實例方法代理到實例變量相關的某個方法上。這樣說可能有點迷糊,我這里列舉一個官方文檔的例子。
require 'forwardable'
class Queue
extend Forwardable
def initialize
@q = []
end
def_delegator :@q, :push, :enq
def_delegator :@q, :shift, :deq
end
沒錯,已經寫完了。這就是Forwardable
模塊的最直接的用法,類Queue
通過擴展模塊Forwardable
,就會得到Forwardable::def_delegator
這樣的類方法,通過這個類方法定義實例方法Queue#enq
并把功能代理到Array#push
這個實例方法上。在解析環境中運行以上代碼。得到的結果跟之前的例子是一樣的。
pry(main)> q = Queue.new
=> #<Thread::Queue:0x007ff00f0ccfe0 @q=[]>
pry(main)> q.enq(1,2,3,4)
=> [1, 2, 3, 4]
pry(main)> q
=> #<Thread::Queue:0x007ff00f0ccfe0 @q=[1, 2, 3, 4]>
pry(main)> q.deq()
=> 1
pry(main)> q
=> #<Thread::Queue:0x007ff00f0ccfe0 @q=[2, 3, 4]>
pry(main)> q.deq(2)
=> [2, 3]
pry(main)> q
=> #<Thread::Queue:0x007ff00f0ccfe0 @q=[4]>
好吧,我也知道廢話很多,其實主要想說明兩件事情
- 有些東西用動態語言來寫雖然運行效率會稍微慢一點,但是勝在靈活,很多時候我們可以更優雅地去實現一些功能。
- Ruby以
Plugin
的方式來增強原來的類的行為,提供一些方便我們日常使用的語法糖讓我們的代碼更精煉。*
如上面的Forwardable
模塊提供了Forwardable#def_delegator
類方法,讓我們可以方便地實現代理功能,而不需要手動地去實現對應的細節。畢竟我們都知道寫得越多錯的越多。
3. 寫在最后
這篇文章作為技術文章似乎有點短,并且瞎扯淡的成分居多。我就是想黑一下Java,然后用Ruby的方式來實現一個簡單的隊列。主要是為了展示一下Ruby的優雅。至于Forwardable
模塊,我對它了解目前還只是停留在應用層面。說實在的我對它的源碼很感興趣,希望后面有機會可以再寫一篇文章深入剖析它的源代碼。(好吧,我也承認我不在這篇文章剖析它的源代碼是因為我根本就還沒看懂它源代碼,他們寫得有點深奧。)
如果您還覺得意猶未盡時間尚早,請用Java去實現相應的隊列以及棧數據結構吧。