Ruby 中的 OpenStruct 浅谈

yufei       5 年, 8 月 前       1572

我...在前一章节 Ruby 中的 struct 浅谈 翻 Struct 类的文档的时候,竟然还发现了一个 OpenStruct ,这... 闹哪样啊...我还以为今天可以休息了...剩下的大量的 Ruby 知识以后再来了解,谁知又冒出一个 OpenStruct

好吧,那今天就把 OpenStruct 也讲完吧...

翻了下 OpenStruct 的官方文档,内容有点多,我们就多分几个小节吧。 本章,我们的主要知识点有

  1. OpenStruct
  2. 数据结构: 创建和操作
  3. OpenStruct 和懒加载
  4. 数据结构背后的实现

加载库 ( 模块 )

首先,需要说明的是,OpenStruct 类是 Ruby 标准库的一部分,但不在核心库中,所以使用前需要先加载相应的库

require 'ostruct'

OpenStruct

一句话介绍 OpenStruct 类: 「 OpenStruct 类用于创建灵活的数据结构 」

OpenStruct 类非常简单易用,且不需要提供严格的成员列表,因为它没有定义任何结构类型,而是之后填充的数据结构

OpenStruct 类是 Ruby 标准库的一部分,处于 stdlib

要了解 OpenStruct 类,第一件要做的事情,就是了解它的祖先链

irb> require 'ostruct'
 => true

irb> OpenStruct.ancestors
 => [OpenStruct, Object, Kernel, BasicObject]

上面的代码中,由于 OpenStruct 不是 Ruby Core 的一部分,因此我们必须使用 require ostruct 加载相应的库

然后,和其它大多数类一样,OpenStruct 也是继承自默认的 Object 对象

不过也有不一样的地方,那就是 OpenStruct 没有包含任何其它模块,与 Struct 不同的是,没有加载 Enumerable 模块,也就是 OpenStruct 类的实例不能进行比较相关操作

数据结构: 创建和操作

OpenStruct::new 方法可以用来创建新的数据结构

new 方法接受一个参数,可选的参数有 HashStruct 或另一个 OpenStruct

require 'ostruct'

computer = OpenStruct.new(ram: '4GB')
computer.class # => OpenStruct

computer.ram    # => "4GB"
computer[:ram]  # => "4GB"
computer['ram'] # => "4GB"

computer.screens = 2
# OR: computer[:screens]  = 2
# OR: computer['screens'] = 2

computer.screens    # => 2
computer[:screens]  # => 2
computer['screens'] # => 2

上面这段代码中

  1. 首先,我们使用一个名为 ram 的属性实例化一个新的 OpenStruct 对象,并将该对象存储在变量 computer
  2. 然后我们通过 3 种不同的方式访问 ram 属性

    1. computer.ram : ram getter 方法
    2. computer[:ram] : 使用一个符号 symbol 作为键的 OpenStruct#[] 方法
    3. computer['ram'] : 使用一个字符串作为键的 OpenStruct#[] 方法
  3. 接着,我们定义了 screens 属性。有 3 种方法可以定义新属性或修改现有属性的值

    1. computer.screens= : screens= setter 方法
    2. computer[:screens]= : 使用一个符号 symbol 作为键的 OpenStruct#[]= 方法
    3. computer['screens']= : 使用一个字符串作为键的 OpenStruct#[]= 方法
  4. 接着,我们使用之前访问 ram 属性同样的方式访问 screens 属性。

与使用固定属性列表定义结构类型的 Struct 类不同,OpenStruct 类定义了可以在数据结构定义范围外动态添加属性的新数据结构 - 就像上面示例中的 screen 属性一样

OpenStruct 和懒加载

为了节省内存并加快对属性的访问,OpenStruct 实例的属性的访问器方法在某些点处是延迟加载的

也就是说,仅在触发一堆定义的操作时才定义方法。

这样,我们的程序只需要定义数据结构工作所需的最少量方法

让我们来看看 OpenStruct 中的懒加载是如何工作的

require 'ostruct'

computer = OpenStruct.new(ram: '4GB')
computer.class          # => OpenStruct

computer.methods(false) # => []
computer[:ram]          # => "4GB"
computer.methods(false) # => []

computer.ram            # => "4GB"
computer.methods(false) # => [:ram, :ram=]

computer[:screens] = 2  # => 2
computer.methods(false) # => [:ram, :ram=, :screen, :screen=]

从上面的代码中可以看出

  1. 在 OpenStruct 实例初始化时,尚未定义 computer.ramcomputer.ram= 方法

  2. 当调用了 computer.ram 获取属性 ram 的值之后,发现已经 computer.ramcomputer.ram= 方法。

    需要注意的是,使用 computer[:ram] 获取属性的值并不会触发定义 computer.ramcomputer.ram= 方法

  3. 直到我们调用了 computer[:ram] = 2,才定义了 computer.screenscomputer.screens= 两个方法

综上所述,我们发现访问者方法仅在以下情况下定义

  1. 第一次调用存在或不存在的属性的 getter 访问器方法
  2. 第一次调用 OpenStruct#[]= 方法,例如范例中的 computer[:ram] = 2

数据结构背后的实现

经过前面的几个小节,想必你已经对 OpenStruct 和数据结构有着相当的了解了。

接下来,我们开始深入了解在创建和操作 OpenStruct 对象时幕后发生的事情

OpenStruct::new 创建的每个数据结构内部都定义了一个哈希 ( hash ) ,这是该数据结属性与其值的对应不安息表

这个内部哈希,通常称之为 「 容器 」 ( container ) ,也称之为 「 表 」( table )

computer.instance_variables # => [:@table, :@modifiable]

需要注意的是,每个属性在添加到 「 表 」之前,都需要将属性 ( 键 ) 转换成一个 symbol

那么,现在我们来看看 computer 表的演变

require 'ostruct'

computer = OpenStruct.new(ram: '4GB')

运行上面的代码,变量 computer 中的表的数据为 {ram: "4GB"}

接着,我们把属性 screens 添加到数据结构 computer

computer[:screens] = 2

添加完毕后,computer 中的表的数据为 {ram: "4GB", screens: 2}

那么,当调用不存在的属性时会发生什么?

例如,当我们调用 computer.cores= 设置方法时 ?

computer.cores = 2

这种情况下,Ruby 的处理逻辑是

「 如果未在给定对象的整个祖先链中定义方法,那么,Ruby 会自动调用此祖先链中第一个定义的 method_missing 钩子方法 」

凑巧的是,OpenStruct 定义了自己的 method_missing 方法。

所以,当调用 computer.cores = 2 时,相当于就是调用 OpenStruct#method_missing 方法

我们详细说明下当调用 computer.cores = 2 时该方法的执行流程

  1. 定义 getter 和 setter 方法,并将作为参数传递的 key 转换为 symbol - 在我们的示例中为 :cores
  2. 将键值对插入内部 「 表 」 中,也就是 @table[:cores] = 2
  3. 返回值为 2

结束语

撒花...真的是为了结束而草率的结束了...

OpenStruct 还是非常有意思的,其实看起来就是一个哈希表,既然是这样,那为什么还要存在一个 OpenStruct 呢 ?

谁直到答案,私聊我,有赏

目前尚无回复
简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.