Leon's Blogging

Coding blogging for hackers.

Ruby - Struct vs OpenStruct

| Comments

在 ruby 當中,經常會要定義一個新的類別,如果覺得每次都要寫 class xxx 太麻煩,就可以用 struct & OpenStruct 快速的產生出來!

Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class People
  attr_accessor :name, :phone

  def initialize(name, phone)
    @name  = name
    @phone = phone
  end

  def to_ary
    [name, phone]
  end
end

a = People.new("foo", 1234)
#=> #<People:0x007fcaabcf5328 @name="foo", @phone=1234>
a.name
#=> "foo"
a.phone
#=> 1234
a.to_ary
#=> ["foo", 1234]

Struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#method 要用 block 來傳遞,Attribute 一開始就固定了
People = Struct.new(:name, :phone) do
  def to_ary
    [name, phone]
  end
end
# => People

a = People.new("foo", 1234)
#=> #<struct People name="foo", phone=1234>
a.name
#=> "foo"
a.phone
#=> 1234
a.to_ary
#=> ["foo", 1234]

也可以直接用繼承的方式

1
2
class People < struct.new(:name, :phone)
end

其他取 Attribute Value 的方法

Class則無法

1
2
3
4
5
6
a[:name]
#=> "foo"
a["name"]
#=> "foo"
a[0]
#=> "foo"

other

不要去 extend 一個 Struct.new - 它已經是一個新的 class。擴展它會產生一個多餘的 class 層級 並且可能會產生怪異的錯誤如果文件被加載多次。

OpenStruct

主要差異點是在於,比 Struct 更有彈性, 因為它可以任意增加 Attribute , 不像 Struct 要先限制好有哪些 Attribute

但比較可惜的是,無法定義 method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#在 console 記得先 require
require 'ostruct'

People = OpenStruct.new
#=> #<OpenStruct>
or
People = OpenStruct.new(name: 'foo', phone: 1234)
#=> #<OpenStruct name="foo", phone=1234>

可以自由新增
People.name = 'foo'
#=> "foo"
People.phone = 1234
#=> 1234
People.age = 18
#=> 18
People
#=> #<OpenStruct name="foo", phone=1234, age=18>

也可以直接用繼承的方式
class Test < OpenStruct
end
  • Struct: 接受的是按順序排列的初始化參數
  • Openstruct: 接受的則是散列表的參數

WHEN TO USE?

  • As a temporary data structure 暫時的 data 結構
  • As internal class data 內部的 class data

也許另一個 class 還不至於明確到可以獨立成一個 class,因此先暫存在別的 class 裡,直到有明確的行為,足夠讓它獨立出去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person
  Address = Struct.new(:street_1, :street_2, :city)

  attr_accessor :name, :address

  def initialize(name, opts)
    @name = name
    @address = Address.new(opts[:street_1], opts[:street_2], opts[:city])
  end
end

leigh = Person.new("Leigh Halliday", {
  street_1: "123 Road",
  city: "Toronto",
})

puts leigh.address.inspect
# <struct Person::Address street_1="123 Road", street_2=nil, city="Toronto", province="Ontario", country="Canada", postal_code="M5E 0A3">
  • As a testing stub
1
2
3
4
5
KCup = Struct.new(:size, :brewing_time, :brewing_temp)
colombian = KCup.new(:small, 60, 85)

brewer = Brewer.new(colombian)
expect(brewer.brew).to eq(true)

官方文件:

參考文件:

Comments