ADZ 學習筆記

Ruby/Rails, Startup, Life

rails 筆記 - 物件設計心得

| Comments

一般的情況在 MVC 下寫程式,能夠設計的物件頂多是 model 拆拆 concerns、寫寫抽象層、或把邏輯封裝到 form/service object 裡,而這些 class 最終都是用在 controller, views,不太有機會去實作一個需要考量 如何與其他物件互動 的物件。

最近這幾個月寫遊戲後端程式讓我有機會好好思考這些問題:

我該如何設計、組織這些複雜的邏輯並且容易維護。

一開始我很直覺的把遊戲共同邏輯拆出 abstraction layer 希望能夠做到像 ActiveRecord::Base 一樣,我可以開一個 class 繼承抽象類別去客製化遊戲事件跟設定值,例如:Racing::Dog < Racing::Base

開發 Racing::Base 過程中最有感想的是實作遊戲設定值的部分,設計物件的目的,是避免一直使用基礎物件 (Hash, Array, String ... etc) 加上 functional programming 去撰寫程式,假設:我們要尋找遊戲賠率最高的狗,可能會有某個 method 去 array 裡面找資料:

def find_the_best_price_dog(game_model)
  Racing::Dog.find { ... }
end

只使用 基礎型別 加上不斷的靠 method 整理資料最大的問題是,如果某天哪個節點的資料結構改變了,程式就炸掉了。而且會變得非常難 debug,因為你很難一眼看出複雜的 Array, Hash 到底是哪裡出問題。

但如果用物件的設計方式,也許我們會有一個 class 叫做 Racing::PlayerCollection 來儲存每個 player,而 collection 在意義上就像一個大盒子,裝著多個 player 的資料,這個大盒子內建就有一個功能是 best_price_player 幫你找到這個盒子內地最佳賠率 player:

game_model.players # return instance of Racing::PlayerCollection

game_model.players.best_price_player # return { name: 'Adz', type: 'dog', rank: 1, price: 0.95 }

當我們能夠找到賠率最佳的狗時,也許某個地方我們會有這樣的程式碼

if Racing::Dog.players.best_price_player[:type] == 'dog'
  # do something

end

這裏我們又操作到基本型別了,一樣的問題,如果未來設定值的 Hash 結構改變,這種程式碼散布世界各地,就會變得非常難改,所以我們也需要把 player 的資料物件化成 Racing::Player 再把程式改寫成:

player = Racing::Dog.players.best_price_player
player.dogs? # true || false

player.price # 0.95

除了以上提到的問題外,複雜的 array, hash 搭配 functional programming 因為沒有明確的 class name,是非常難以測試的,即使有 test,反而會更自找麻煩,日後結構改變,不只程式要改,連 test 也會改很大。

Comments

comments powered by Disqus