Ruby Ruby 中的 OpenStruct 详解

neverlandxy_naix · 发布于 2014年9月19日 · 最后由 zztczcx 回复于 2016年8月26日 · 3741 次阅读
2019
本帖已被设为精华帖!

什么是 Struct?

在 C 语言中, 有一种数据类型叫做 Struct, 定义一个 Struct 相当于定义了一个自定义的数据类型, 里面可以包含各种不同的其它数据类型. 例如:

struct person
{
  int age;
  char name[50];
  char sex;
};

同样在 Ruby 中, 也有 Struct, 纯 C 实现, 用于快速声明一个 Class, 例如:

Person = Struct.new(:age, :name, :sex)
me = Person.new(24, 'Spirit', 'male')
me.age  # => 24
me.name = 'Test'
me.name # => 'Test'
me.height # => NoMethodError

当然, 我们也可以通过定义 Class 来实现同样的功能, 但是 Struct 的快速声明功能是直接定义 Class 无法实现的.

OpenStruct 详解

在上面的实例中, 可以看到, me 无法动态响应任意的属性, OpenStruct 类恰巧就完成了这件事情.

还是上面那个例子:

require 'ostruct'

me = OpenStruct.new(age: 24, name: 'Spirit', sex: 'male')
me.height # => nil
me.height = '178'
me.height # => '178'

翻看 OpenStruct 的源码, 可以看到其核心为 #initialize(), #new_ostruct_member()#method_missing() 三个方法:

def initialize(hash=nil)
  @table = {}
  if hash
    hash.each_pair do |k, v|
      k = k.to_sym
      @table[k] = v
      new_ostruct_member(k)
    end
  end
end

初始化, 一旦传入的参数为 hash, 则把 hash 的键值对一一保存到实例变量 @table 中, 并且调用 #new_ostruct_member() 方法

def new_ostruct_member(name)
  name = name.to_sym
  unless respond_to?(name)
    define_singleton_method(name) { @table[name] }
    define_singleton_method("#{name}=") { |x| modifiable[name] = x }
  end
  name
end

为每一个传入的 name(hash 的 key) 都生成两个实例方法 #name#name=, 并且定义其值为实例变量 @table 中对应的 key 的值

def method_missing(mid, *args)
  mname = mid.id2name
  len = args.length
  if mname.chomp!('=')
    if len != 1
      raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
    end
    modifiable[new_ostruct_member(mname)] = args[0]
  elsif len == 0
    @table[mid]
  else
    err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
    err.set_backtrace caller(1)
    raise err
  end
end

重写 #method_missing 方法

当初始化的时候没有传入 hash, 或者让对象响应一个任意的方法时:

如果需要响应方法为 mname=, 并且实参为一个, 那么调用 #new_ostruct_member() 方法, 为实例定义两个方法 #mname#mname=, 下次调用该方法时, 则不需要经过 method_missing 这一步骤

如果需要响应的方法为 mname, 并且没有实参, 那么直接返回 @table 中对应的 k 的值

事实上, 除非为 @table 注入一个 { mname: value } 的键值对或者调用 obj.mname=value, 否则直接调用 obj.mname 经过 method_missing 后返回的值, 都为 nil

me = OpenStruct.new(age: 24, name: 'Spirit', sex: 'male')
me.height # => 'nil'
me.instance_eval do
  @table[:height] = 178
end
me.height # => '178'

ok, OpenStruct 详解完毕, 其余方法可自行挖掘.

最后说一句, OpenStruct 由于进行了 method_missing 判断, 其性能与 Struct 相比, 可谓是天差地别. http://stackoverflow.com/questions/1177594/ruby-struct-vs-openstruct

共收到 13 条回复
14143

喜欢

1286

赞一个

1638

赞。

6553

code walkthrough 什么的最有爱了

96

这个OpenStruct效率如何?有坑吗?

7974

赞。 性能确实木有自带的Struct好,不过某些读取yml之类的代码能简洁很多。我在做的内部测试脚本里的对象描述就应用到了OpenStruct,RecursiveOpenStruct就是一个增强版的OpenStruct,复杂的symbol也能描述,看源码也很好理解。

2019

#6楼 @jianliao 没啥坑, 效率上, 看你怎么用了, 读取写yml文件是没什么问题的.

2019

#7楼 @davidlichao 在项目中我用这个 settingslogic, 比 Struct 更方便.

11587

自己写了一个Struct,可以满足简单需求。 class LStruct

def initialize(hash) @attrs = {} hash.each_pair do |k, v| @attrs[k.to_s] = v end end

def method_missing(attr, *args) name = attr.to_s if name =~ /=$/ @attrs[name.chop] = args[0] else @attrs[name] end end end

11587

并附上效率测试结果: user system total real struct 0.020000 0.000000 0.020000 ( 0.017825) ostruct 0.730000 0.010000 0.740000 ( 0.733454) lstruct 0.160000 0.000000 0.160000 ( 0.162041) 可以看到,struct效率最高,ostruct 效率最低,自己实现的由于比较简单,效率在两者之间。

Code: require 'benchmark' require 'ostruct'

n = 50000 Benchmark.bm do |b| b.report('struct') { P = Struct.new(:sex, :age) n.times do |x| P.new(:'男', n % 60) end } b.report('ostruct') { n.times do |x| OpenStruct.new(:sex => '男', :age => n % 60) end } b.report('lstruct') { n.times do |x| LStruct.new(:sex => '男', :age => n % 60) end } end

10594

学习啦

6376

看過測試, openstruct 內存使用驚人。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册