Ruby 中的 struct 浅谈

yufei       6 年, 3 月 前       2455

Ruby 中也有结构体 ( struct ) ,这点经常会被忽略,存在感实在不敢恭维....一个完全面向对象的编程语言,实在想不出需要用到 struct 的场景。

即使偶尔能想到,也会用其它的方法代替,毕竟,struct 感觉是 C 语言 时代的东西。

而且,忘记说了,Ruby 中的 Struct 也是一个类,对的,你没看错,是一个类。

本着探奇的心里,我们还是来讲讲 Ruby 中的这个 struct 吧。

本章节会涉及到以下几个知识点

  1. Struct
  2. 结构体类型和结构体
  3. Ruby 结构体背后的那些实现

Struct 类

任何一门语言,除了变态的 C++ ,结构体的主要作用就是纯粹的存储数据。虽然不少的语言也能够给 Struct 定义一些方法,但那不是它的初衷。

如果既需要保存数据,又需要对数据进行各种逻辑操作的方法,那还不如直接定义一个类或对象。

Ruby 中的结构体也是虚拟数据容器。与对象或类不同,它用于捆绑和提供一组没有任何逻辑的信息。也就是纯粹只是数据容器。

Ruby 中的 Struct 类还为它包含的每个属性提供了一对 getter/setter 方法,这类似于 attr_accessor 方法

如果你不熟悉 Ruby 中的 attr_ * 方法,可以阅读我们的 Ruby 中的 Attributes 文章。

Ruby 中的 Struct 类是结构类型构建器。该类负责定义可以在之后生成结构实例的新结构类型

让我们来看看它的祖先链

irb> Struct.class
 => Class
irb> Struct.ancestors
 => [Struct, Enumerable, Object, Kernel, BasicObject]

从祖先链中可以看出,Struct 类的默认祖先也是 Object 对象。

Struct 类的祖先中还包括 Enumerable 模块,负责向一个类添加一堆搜索,排序和遍历方法

如果你仔细观察, 其实,就会发现,Struct 类与 Array 和 Hash 类有着完全相同的祖先链

结构体类型和结构体

一个结构体类型是一个蓝图 ( blueprint ) 或者说是类,它包含一个不可变的属性列表 - 也称为成员

一个结构是则是该蓝图 ( 对象 ) 的内存表示。

现在让我们看看如何创建结构体类型。

Struct::new 方法是结构体类型构建器。它允许我们定义与作为参数传递的一组已定义成员关系的新结构类型

简单的说就是传递一组属性名给 Struct::new 方法来创建一个新的结构体类型

Address = Struct.new(:street, :city, :zip)
home = Address.new('Broadway', 'NYC', 10040)

上面的代码中,我们定义了一个新的结构体类型 Address ,该 Address 结构体类型包含三个成员,分别是 :street:city:zip

第二行代码,我们使用 'Broadway', 'NYC', 10040 三个参数创建了一个新的 Address 结构体的实例,并把它赋值给 home 变量。

Address.new('Broadway','NYC',10002) 的每个参数都按给定顺序匹配 Struct.new(:street,:city,:zip) 的相应参数

home.street # => "Broadway"
home[:city] # => "NYC"
home['zip'] # => 10040

home.not_exist    # => NoMethodError: undefined method `not_exist'
home[:not_exist]  # => NameError: no member 'not_exist' in struct
home['not_exist'] # => NameError: no member 'not_exist' in struct

上面的代码中,我们可以看到,有三种方式可以访问一个结构体实例的成员

  1. home.street :使用的是 street getter 访问方法
  2. home[:city] :使用 symbol 类型键的 Struct#[] 方法
  3. home['zip' :使用 string 类型键的 Struct#[] 方法

我们也看到了,如果尝试访问不存在的成员,则会抛出 NoMethodErrorNameError,具体取决于访问此成员的方式

除了获取访问成员之外,还可以修改给定结构的成员值

home # => #<struct Address street="Broadway", city="NYC", zip=10040>

home.street = 'Opéra'
home[:city] = 'Paris'
home['zip'] = 75009

home # => #<struct Address street="Opéra", city="Paris", zip=75009>

与访问成员的三种方式对应的,修改成员也有三种方式

  1. home.street= :使用的是 street= setter 访问方法
  2. home[:city] :使用 symbol 类型键的 Struct#[] 方法
  3. home['zip' :使用 string 类型键的 Struct#[] 方法

另外,从上面的代码中,我们还注意到,如果尝试修改不存在的成员,则会引发 NoMethodErrorNameError,具体取决于修改此成员的方式

现在我们已经熟悉结构体和结构类型,接下来我们深入研究 Ruby 内部是如何定义结构类型的

Ruby 结构体背后的那些实现

与 Ruby 中的类一样,Struct::new 方法可以实例化一个 Struct 类型的对象

irb> Struct.allocate
TypeError (allocator undefined for Struct)
irb> Struct.methods(false)
 => [:new]

实际上,Struct#allocate 方法 - 负责分配包含 Struct 对象所需的内存空间 - 在 Struct 类定义中是未定义的

因此,Struct 类无法分配所需的内存来实例化 Struct 类型的对象

究其原因,是因为 Struct 类重写了 BasicObject#new 方法

那么,如果我们不能实例化 Struct,那么如何定义结构体类型 ?

谈起如何实现,这,讲起来还有点那么的神奇,毕竟总让人觉得这个类是可实例化的。

其实,所有神奇之处追根究底还是在 Struct#new 方法中定义。

Struct#new 方法并没有实例化 Struct,而是创建了自己的子类

Address = Struct.new(:street, :city, :zip)

Address.class      # => Class
Address.superclass # => Struct

上面的代码中,Address 常量实际上是一个继承自 Struct 类的子类。

这样就允许了 Address 访问定义在 Struct 类中的所有方法和内部的一些逻辑

也可以说,结构类型实际上是一个从 Struct 类继承的命名类,而结构只是这个命名类的一个实例。

这个强大的设计允许我们的结构类型享受类在 Ruby 中提供的所有机制,如类开放,继承,混合等...

例如,我们可以重新打开 Address 类来添加 full_address 方法

Address = Struct.new(:street, :city, :zip)

class Address
  def full_address
    "#{street} #{city} #{zip}"
  end
end

home = Address.new('Broadway', 'NYC', 10040)

home.full_address # => "Broadway NYC 10040"

这是 Struct::new 方法的基本用法。

其实,Struct::new 方法还提供了另一种创建一个结构体类型的方法,我们直接看示例代码即可

Struct.new('Address', :street, :city, :zip)

Struct::Address.superclass # => Struct

home = Struct::Address.new('Broadway', 'NYC', 10040)

home.class # => Struct::Address

通过将结构类型名称作为第一个参数传递给 Struct::new ,该方法自动在 Struct 类的作用域范围内定义一个新类,这个新类也继承自 Struct

然后我们可以实例化新定义的 Struct::Address 结构类型并将结构存储在 home 变量中

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

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

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