天天看點

《Effective Ruby:改善Ruby程式的48條建議》一第10條:推薦使用Struct而非Hash存儲結構化資料

本節書摘來自華章出版社《effective ruby:改善ruby程式的48條建議》一書中的第2章,第2.5節,作者 [美]彼得 j.瓊斯(peter j. jones),更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視

哈希表是ruby程式員經常使用的一種有用的、通用的資料結構。hash類提供了使用哈希表的簡單的接口,與數組一樣,它是ruby的重要部分之一,該類有自己專用的文法來建立新的執行個體。當需要使用鍵值對時,hash類絕對是首選。

事實上,ruby程式員在任何時候都會使用哈希,甚至方法的參數關鍵字也是使用hash類文法糖來實作的。哈希如此通用,是以能被用來對類型進行模拟,比如數組、集合,甚至基本對象。在oop語言中,當用到結構化資料時,我們往往有比哈希更好的選擇,在ruby中也不例外。讓我們看一個典型的使用hash類的例子,然後思考如何替換掉這個結構以使其更合适。

假定你對探索天氣資料感興趣,并從本地氣象局擷取了年度天氣資料。拿着海洋與大氣管理局(noaa)提供的一個csv檔案,你計劃将其載入數組并進行分析。csv檔案中每行資料都包含一個月的溫度統計資訊。你決定提取每行中你感興趣的一些列,并用哈希數組來存儲。思考一下:

《Effective Ruby:改善Ruby程式的48條建議》一第10條:推薦使用Struct而非Hash存儲結構化資料

這裡沒什麼特殊的。csv檔案中的每一行都被翻譯為一個哈希并随後插入數組。完成initialize方法後,你将獲得一個固定格式的哈希數組,即所有的哈希對象都包含相同的鍵,不過值是不同的。本質上講,這個哈希數組表示的是一些對象的集合,不能通過getter方法通路其屬性,要通路其屬性,你得通過哈希索引操作符來完成。這可能是個小問題,不過它會對annualweather類的接口産生重大影響。

你不應該将這個哈希數組通過公共接口向外暴露,因為每個哈希的鍵都包含内部實作細節。如果沒有類級别的文檔,其他程式員将不得不讀完整個initialize方法的定義才能知道哪個鍵對應着csv中的哪一列。如果initialize是設定鍵值的唯一方法,那這可能不會造成很大的負擔,然而當類逐漸成熟起來,情況可能會有所變化。這就是使用哈希模拟對象表達結構化資料時的缺點,哈希作為公共接口時是沒有通路限制的。

每次你想在類内部使用該哈希時,你得回頭看看initialize方法進而想起哪些鍵是可用的。同樣的,隻要鍵值的設定都在一個方法内,就沒有太大的負擔。不過我們來思考一下這樣的場景:你嘗試建立一個新鍵。在@readings數組裡,每個月的資料都有最高溫度和最低溫度。你想知道一年裡的平均溫度,是以你也需要知道每個月的平均

溫度。

《Effective Ruby:改善Ruby程式的48條建議》一第10條:推薦使用Struct而非Hash存儲結構化資料

計算每個月的平均溫度非常簡單。即使如此,如果@readings數組中的每個對象都能響應一個mean方法計算平均值,将會把那段邏輯從這個方法中抽象出去。将這個方法塞進每個哈希裡是可以做到的,但是在本例中會不必要地使代碼難以了解。(畢竟我們不是在寫javascript代碼。)

在ruby中使用哈希代替特别簡單的類是經常發生的。有時這完全沒有問題,但是通常我們真的應該為這類對象建立其專門的類型。如果你和我一樣懶,認為建立一個新類這麼簡單的事情似乎是一種不必要的雜活兒,很幸運,這正是struct類可以幫我們

做的。

表面上看,使用struct類很像是在c++中建立一個新的struct類型。然而你如果深挖一下,就會發現這更像是生成類而非資料結構。使用struct非常簡單,隻需調用struct::new方法并附上屬性清單。該方法的傳回值幾乎就是一個新類,它包含每個屬性的getter和setter方法。新類也有initialize方法,能夠分别初始化每個屬性

的值。

為了替換目前使用的哈希,我們隻需在initialize方法中做一點微小的改變。

《Effective Ruby:改善Ruby程式的48條建議》一第10條:推薦使用Struct而非Hash存儲結構化資料

如你所見,将struct::new方法的傳回值賦給一個常量是常見的實踐。這允許你像類一樣使用這個常量,并利用它建立對象。這個單行的代碼也讓你能清楚地知道這個新類産生的對象提供了哪些方法。這是對特殊的哈希的重大改進。讓我們看看這個變化對mean方法的影響。

《Effective Ruby:改善Ruby程式的48條建議》一第10條:推薦使用Struct而非Hash存儲結構化資料

mean方法不需要改變太多,但現在它看起來更有面向對象程式設計的感覺了。通過getter方法通路屬性high和low也有一點副作用。如果把屬性名拼錯,會引發一個nomethoderror異常。使用哈希時不會有這個問題,因為在哈希中試圖通路非法的鍵隻會傳回nil而不會引發異常,不過這往往意味着在之後的代碼中,你将被卷入一個更難發現的typeerror異常。

struct類本身比你第一次使用它時更強大。除了屬性清單,struct::new方法還能接收一個可選的塊,在新類的上下文中被評價執行。這很拗口,但它實質上是說,我們能在塊中定義執行個體方法和類方法。比如,下面是我們如何定義mean方法的:

《Effective Ruby:改善Ruby程式的48條建議》一第10條:推薦使用Struct而非Hash存儲結構化資料

當你覺得建立一個新類過于笨重時,struct就非常有用了。哈希隻能定義一堆相同格式的無關的鍵,struct卻能讓你定義執行個體方法和類方法。當你想為對象增加一些簡單操作時,這完美極了。它們的公共接口也是類型良好的,更适合于annualweather類的使用者。通過為@readings數組開啟attr_reader方法,可以解決之前對通過annualweather向外暴露一個哈希數組的顧慮。我不得不說這是一個大的改進。

要點回顧

在處理結構化資料時,如果建立一個新類不那麼合适時,推薦使用struct而非hash。

将struct::new的傳回值賦給常量,并像類一樣使用它。

繼續閱讀