Ruby 类与结构

问题描述 投票:0回答:7

我见过使用 Structs 来包装类内的属性和行为的代码库。 Ruby 类和结构有什么区别?什么时候应该使用其中一种而不是另一种。?

ruby
7个回答
32
投票

来自结构文档

Struct 是一种使用访问器方法将多个属性捆绑在一起的便捷方法,而无需编写显式类。

Struct 类生成新的子类,其中包含一组成员及其值。对于每个成员,都会创建一个类似于 Module#attr_accessor 的读取器和写入器方法。

因此,如果我想要一个可以访问名称属性(读和写)的

Person
类,我可以通过声明一个类来实现:

class Person
  attr_accessor :name

  def initalize(name)
    @name = name
  end
end

或使用结构:

Person = Struct.new(:name)

在这两种情况下我都可以运行以下代码:

 person = Person.new
 person.name = "Name"
 #or Person.new("Name")
 puts person.name

什么时候使用?

正如描述所述,当我们需要一组可访问属性而无需编写显式类时,我们使用结构。

例如我想要一个点变量来保存 X 和 Y 值:

point = Struct.new(:x, :y).new(20,30)
point.x #=> 20

更多示例:


11
投票

补充其他答案,有些事情你不能用结构来做,有些事情你不能做。

例如,您无法创建没有参数的结构

Bar = Struct.new
=> ArgumentError: wrong number of arguments (given 0, expected 1+)

Bar = Struct.new(:bar)
bar = Bar.new(nil)
bar.class
=> Bar

但是,课程可以让你做到这一点:

class Foo; end
foo = Foo.new
foo.class
=> Foo

您无法为结构参数设置默认值

Bar = Struct.new(bar: 'default')
=> ArgumentError: unknown keyword: bar

Bar = Struct.new(bar = 'default')
=> NameError: identifier default needs to be constant

但是你可以用一个类来做到这一点,要么传递一个散列,参数可以是任何顺序,甚至是缺失:

class Bar
  attr_reader :bar, :rab
  def initialize(bar: 'default', rab:)
    @bar = bar
    @rab = rab
  end
end

bar = Bar.new(rab: 'mandatory')
bar.rab
=> 'mandatory'
bar.bar
=> 'default'

bar = Bar.new(rab: 'mandatory', bar: 'custom_value')
bar.rab
=> 'mandatory'
bar.bar
=> 'custom_value'

或者直接传递值,参数应该以相同的顺序给出,默认的总是在最后:

class Bar
  attr_reader :rab, :bar
  def initialize(rab, bar = 'default')
    @rab = rab
    @bar = bar
  end
end

bar = Bar.new('mandatory')
bar.rab
=> 'mandatory'
bar.bar
=> 'default'

bar = Bar.new('mandatory', 'custom_value')
bar.rab
=> 'mandatory'
bar.bar
=> 'custom_value'

您不能使用结构体执行任何操作,除非您以这种超级详细的方式为参数设置默认值:

A = Struct.new(:a, :b, :c) do
  def initialize(a:, b: 2, c: 3)
    super(a, b, c)
  end
end

(示例取自此答案

您可以在 Struct 中定义方法

Foo = Struct.new(:foo) do
  def method(argument)
    # do something with argument
    end
  end
end

结构对于创建数据对象非常有用,就像答案之一中提到的点示例一样。

我有时使用它们以简单的方式在测试中创建假货和模拟。有时 RSpec

allow(foo).to receive(:blah)
等可能有点过于冗长,而使用 Struct 就简单多了。


5
投票

Struct 是用于创建类的 Ruby 简写。在适用的情况下使用 Struct 可以简化您的代码。 https://www.rubytapas.com/2012/11/07/episode-020-struct/

对此有很好的讨论

3
投票

使用 Struct 工具而不是创建 ruby 类的一些优点如下:

  1. 一个 ruby 类需要 8 行来定义所有必要的属性访问器和构造函数方法,而 Struct 只需要一行代码

  2. 将结构分配给变量后,我可以使用类似散列的下标语法获取和设置值,并且在其中,我可以互换地使用符号或字符串作为键

  3. 结构体有一个相等运算符。 struct 定义了它,以便具有相同属性的实例被认为是相等的

  4. 与使用 attr_accessor 定义的属性不同,结构体可以使用诸如members(将返回实例属性)、each 或each_pair(其中您可以迭代属性的名称及其值)等方法来自省和迭代其属性

  5. 结构体还包括可枚举,因此我们也有完整的可枚举方法

来源:优雅的开发网站

就系统性能而言,这篇文章肯定了类比结构更快


1
投票

我想@sam_forgot 建议的基准。这种比较不太公平。 如今,类和结构都支持关键字参数。在每个上使用关键字参数都会产生相反的效果,正如您从我的示例结构中看到的那样,带有关键字参数的性能与类没有太大不同。

require 'benchmark'

REP=1000000

SUser = Struct.new(:name, :age)
SUserK = Struct.new(:name, :age, keyword_init: true)

DATA = { name: "Harry", age: 75 }
DATA2 = DATA.values

class CUser
  attr_accessor :name, :age
  def initialize(name, age)
    @name = name
    @age = age
  end
end

class CUserK
  attr_accessor :name, :age
  def initialize(name:, age:)
    @name = name
    @age = age
  end
end

Benchmark.bmbm do |x|
  x.report 'Struct create and access, without keyword arguments' do
    REP.times do
      user = SUser.new(DATA)
      "#{user.name} - #{user.age}"
    end
  end

  x.report 'Struct create and access, with keyword arguments' do
    REP.times do
      user = SUserK.new(**DATA)
      "#{user.name} - #{user.age}"
    end
  end

  x.report 'Class create and access, without keyword arguments' do
    REP.times do
      user = CUser.new(*DATA2)
      "#{user.name} - #{user.age}"
    end
  end 

  x.report 'Class create and access, with keyword arguments' do
    REP.times do
      user = CUserK.new(DATA)
      "#{user.name} - #{user.age}"
    end
  end
end

Rehearsal ---------------------------------------------------------------------------------------
Struct create and access, without keyword arguments   3.484609   0.011974   3.496583 (  3.564523)
Struct create and access, with keyword arguments      0.965959   0.005543   0.971502 (  1.007738)
Class create and access, without keyword arguments    0.624603   0.003999   0.628602 (  0.660931)
Class create and access, with keyword arguments       0.901494   0.004926   0.906420 (  0.952149)
------------------------------------------------------------------------------ total: 6.003107sec

                                                          user     system      total        real
Struct create and access, without keyword arguments   3.300488   0.010372   3.310860 (  3.339511)
Struct create and access, with keyword arguments      0.876742   0.004354   0.881096 (  0.903551)
Class create and access, without keyword arguments    0.553393   0.003962   0.557355 (  0.568985)
Class create and access, with keyword arguments       0.831672   0.004811   0.836483 (  0.850224)

0
投票

实际性能差异相当大,Ruby 2.6.3p62 中的示例行为:

                          user       system     total     real
Struct create and access  3.052825   0.005204   3.058029  (3.066316)
Class create and access   0.738605   0.001467   0.740072  (0.743738)

示例代码:

require 'benchmark'

REP=1000000
SUser = Struct.new(:name, :age)
DATA = { name: "Harry", age: 75 }

class User
  attr_accessor :name, :age
  def initialize(name:, age:)
    @name = name
    @age = age
  end
end

Benchmark.bm 20 do |x|
  x.report 'Struct create and access' do
    REP.times do
      user = SUser.new(DATA)
      "#{user.name} - #{user.age}"
    end
  end
  x.report 'Class create and access' do
    REP.times do
      user = User.new(DATA)
      "#{user.name} - #{user.age}"
    end
  end
end

0
投票

您可以将 Struct 视为比 Class 更高级的东西。

经验法则:对于像 Ruby 这样的高级语言,您希望默认使用高级东西,即 Struct。并且只有当您需要 Struct 未提供的某些功能时(例如默认情况下无法访问属性、可忽略的线性性能提升或继承以轻松扩展属性列表),才使用 Class。

© www.soinside.com 2019 - 2024. All rights reserved.