struct in ruby

作者:周星 发布:2017-10-07

   上次和大家一起学习了 OpenStruct,今天我们来学习 ruby 中的 Struct。

    Struct 不需要 require 任何东西,直接拿过来就能用,它也是 Ruby 中一种很常见的数据结构,通过 Struct.new,我们可以很方便的得到一个类对象(在 Ruby 中类也是对象,我们不会深入讨论这个细节),这个类对象既可以被赋值为变量也可以是常量,然后我们可以通过这个类随意的初始化对象,注意属性名一定是 symbol,请看下面这个例子:

irb(main):001:0> Person = Struct.new :name, :age
=> Person
irb(main):002:0> Person.class
=> Class
irb(main):003:0> Person.superclass
=> Struct

Person 是一个 class,它是 Struct 的子类,也就是说通过 Struct.new 返回的是 Struct 的子类,我们可以通过这个子类随意的初始化对象。

我们继续看下面这个例子:

irb(main):004:0> edward = Person.new "Edward", "25"
=> #<struct Person name="Edward", age="25">
irb(main):005:0> edward.name
=> "Edward"
irb(main):006:0> edward.age
=> "25"
irb(main):007:0> edward['name']
=> "Edward"
irb(main):008:0> edward[:name]
=> "Edward"

我们初始化 'edward' 为 Person 类的一个对象,然后可以通过很多种方式(Hash、方法)获取它的属性值。

Struct 还允许我们在 new 一个类对象的时候传入一个 block,在这个 block 里我们可以定义方法:

irb(main):001:0> Person = Struct.new :name, :age do
irb(main):002:1*   def self_introduce
irb(main):003:2>     p "My name is #{name}, I am #{age}"
irb(main):004:2>   end
irb(main):005:1> end
=> Person
irb(main):006:0> edward = Person.new "Edward", "25"
=> #<struct Person name="Edward", age="25">
irb(main):007:0> edward.self_introduce
"My name is Edward, I am 25"
=> "My name is Edward, I am 25"

通过上面的例子我们看到了如何在一个 Struct 中定义方法,那么问题来了: “如何在 Struct 里定义 initialize 方?”,有时您可能会有这个需求,但是我可能会建议您不要这么做,重新设计一下您的程序,如果您非要这么做,请参考下面这个例子:当 Person 没有年龄的时候,给它默认值为 20 岁:

Person = Struct.new(:name, :age) do
  def initialize(*args)
    super(*args)
    self.age = 20 if age.nil?
  end
end

ming = Person.new 'ming'
=> #<struct Person name="ming", age=20>

在初始化一个类时必须指定其所有的属性,如果在对象上调用了不存在的属性,则会报错

irb(main):011:0* edward.address = "sichuan province"
NoMethodError: undefined method `address=' for #<struct Person name="Edward", age="25">

Struct.new 方法还有另外一种使用方法,不过我很少见有人用,此处只作参考

irb(main):002:0* Struct.constants
=> [:Tms]
irb(main):003:0> s = Struct.new "Point", :x, :y
=> Struct::Point
irb(main):004:0> Struct.constants
=> [:Tms, :Point]
irb(main):005:0> t = Struct.new "itWillNotWork", :x, :y
NameError: identifier itWillNotWork needs to be constant
	from (irb):5:in `new'
	from (irb):5
	from /Users/xingzhou/.rbenv/versions/2.1.5/bin/irb:11:in `<main>'

第一个参数为字符串类型,作为 Struct 的常量,所以必须为大写,变量 s 仍然为一个类对象,其为 Struct 类的子类

关于 Struct 的一些行为:

1. Struct 具有数组的各种方法,它不但可以 to_h,还可以 to_a

irb(main):003:0* Person = Struct.new :name, :age
=> Person
irb(main):004:0> edward = Person.new "Edward", 25
=> #<struct Person name="Edward", age=25>
irb(main):005:0>
irb(main):006:0* edward.to_a
=> ["Edward", 25]
irb(main):007:0> edward.to_h
=> {:name=>"Edward", :age=>25}
irb(main):008:0> edward.count
=> 2
irb(main):009:0> edward.values
=> ["Edward", 25]

2. 在 new 一个对象时,自动生成了 getter 和 setter 方法

irb(main):001:0> Person = Struct.new :name, :age
=> Person
irb(main):002:0> edward = Person.new "Edward", 25
irb(main):003:0> Person.instance_methods(false)
=> [:name, :name=, :age, :age=]

3. eql? 和 hash

当两个对象的属性值相等,则它们是 eql 的,而且它们拥有一样的 hash code,这是一个非常重要的特性

irb(main):001:0> Person = Struct.new :name, :age
=> Person
irb(main):002:0> e = Person.new "e", 20
=> #<struct Person name="e", age=20>
irb(main):003:0> f = Person.new "e", 20
=> #<struct Person name="e", age=20>
irb(main):004:0> e.eql? f
=> true
irb(main):005:0> e.hash
=> -2421623419594509280
irb(main):006:0> f.hash
=> -2421623419594509280

建议您试着执行一下 Person.methods,看看还有哪些有趣的方法。

总结:

使用 Struct,当你:

  • 需要一个数据结构且属性(字段)是确定的
  • 需要一个有结构的 Hash key
  • 想要快速的创建一个带有一些属性的类
  • 需要在调用对象不存在属性的时候报错

使用 OpenStruct,当你:

  • 在 runtime 属性的个数是确定的,但是 development time 时会频繁变化
  • 想要快速得到一个对象,和对应的 setter/getter 方法
  • 想返回一个对象,这个对象的属性名和值是动态的(请参考 Rails_config 的源码)

使用 Hash,当你:

  • 属性个数等均不确定
  • 属性个数可能无限长,比如在一个文件中读取 key/value,然后使用脚本处理它

 

支付宝扫码赞助博主


评论(0)