模擬class物件:Ruby當中Struct及OpenStruct的使用
為什麼我們需要模擬class物件呢?主要是一個物件有一些屬性需要存取,例如一篇文章Post
底下需要title
和content
兩個屬性,用class來存取就是用牛刀殺雞,太過複雜,用簡單的Hash
存取即可。
但Hash
其實在某些功能上過於簡單,存取的功能較不方便,假如要模擬的class更為複雜,就需要OpenStruct
協助。
OpenStruct
OpenStruct
跟Hash
一樣可以自帶屬性:
require 'ostruct'
# OpenStruct class不包含在原本Core物件當中,因此需要先require
book = OpenStruct.new(title: "Harry Potter", episodes: 7)
# 讀取
book.title # => "Harry Potter"
book[:title] # => "Harry Potter"
book["title"] # => "Harry Potter"
# 存入
book.title = "Holmes" # => "Holmes"
book[:title] = "Holmes" # => "Holmes"
book["title"] = "Holmes" # => "Holmes"
# 隨意新增屬性
book.content = "book content"
book.content
# => "book content"
以上可以看出,OpenStruct
最方便的地方就是可以把屬性當做method來處理,可以直接指定屬性內容、直接讀取,且不管使用string或symbol當做key都通用。
如果要把一整個Hash的數值import到裡面也很簡單:
hash = Hash(:popularity => "well-known", :author => "J.K. Rowling")
hash.each do |key, value|
book[key] = value
end
OpenStruct
的自由程度比較接近Hash
,可以隨時新增及定義屬性,但沒辦法定義method。如果要加入method,則必須使用Struct
。
Struct
Struct
稍微複雜一些,比較接近於原本class的使用。
# 宣告時需要先行定義屬性
Book = Struct.new(:title, :episodes)
book = Book.new("Harry Potter", 7)
book
# => #<struct Book title="Harry Potter", episodes=7>
# 讀取的方式與OpenStruct一樣自由
book.title
# => "Harry Potter"
book[:title]
# => "Harry Potter"
book["title"]
# => "Harry Potter"
# 但無法隨時新增數值
book.content = "book content"
# => NameError: no member 'content' in struct
# 若在宣告Struct instance時,未帶入的變數會自動變成nil
book = Book.new("Harry Potter")
book.episodes
# => nil
# 同樣的,宣告時帶入太多參數會產生錯誤
book = Book.new("Harry Potter", 7, "other stuff")
# => ArgumentError: struct size differs
但Struct
很威的地方是可以在定義時帶入block,並定義method:
Book = Struct.new(:title, :episodes) do
def excerpt
"The book title is #{title}, which contains #{episodes} episodes."
end
end
book = Book.new("Harry Potter", 7)
book.excerpt
# => "The book title is Harry Potter, which contains 7 episodes."
實際應用
我自己最常用於寫Rails測試的時候使用Struct,例如撰寫RSpec的unit test要將其他class物件使用mock區隔開來。
原本的code
class Post < ActiveRecord ::Base
def duplicate
Tool.new.duplicate
end
end
一般撰寫unit test,為求獨立測試,所以會將其他class物件隔離,所以我們就用Struct
將上方的Tool
這個class給mock掉。
測試code
require 'spec_helper'
describe Post do
it "#duplicate" do
stub_const("Tool", Struct.new(){
def duplicate
"It is duplicated!"
end
})
expect(subject.duplicate).to be_truthy
end
end
執行時,由於Tool
這個class已經被stub_const
,所以就不會去讀取原本的class內容,而是讀取我在測試中設定的Struct
,並回傳"It is duplicated!"
字串。成功的執行了這個unit test!
效能
在效能比較上,Struct
和OpenStruct
有非常大的差異。如StackOverflow上這篇比較所述,可以看出在非常簡單的物件存取上,大概會有5~6倍的效能差異。如果在Application內實際應用大量的OpenStruct,可能會有效能的問題,建議優先refactory為Struct
物件。
延伸閱讀:Logical Bricks圖片來源:WikiMedia