【问题标题】:Ruby Class vs StructRuby 类与结构
【发布时间】:2014-11-10 11:48:55
【问题描述】:

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

【问题讨论】:

    标签: ruby


    【解决方案1】:

    来自Struct docs

    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
    

    什么时候用?

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

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

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

    更多示例:

    【讨论】:

    • 没错,当两者都做同样的事情时。为什么我们既有 Class 又有 Struct。我的论点是何时使用类以及何时在应用程序中使用结构。它们适合哪些用例?
    • 对于点示例,您还可以查看 ostruct(在链接中提到)。但基本思想是不需要创建一个类来分组访问属性
    • “为什么我们同时拥有 Class 和 Struct”——我不明白你的意思。 Struct 是一个生成类的类。它只是为了节省您的打字时间。
    • 对我来说这听起来很奇怪:“当我们需要一组可访问的属性而不需要编写显式类时,我们会使用 Structs。”这导致必须编写显式结构。显式结构与显式类?
    【解决方案2】:

    要补充其他答案,有些事情你不能用 Struct 做,有些事情你可以做。

    例如,你不能创建没有参数的结构

    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'
    

    你不能用 Structs 做任何事情,除非你以这种超级详细的方式为你的参数设置默认值:

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

    (示例取自this answer

    您可以在 Struct 中定义方法

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

    结构可用于创建数据对象,例如答案之一中提到的点示例。

    我有时会使用它们以简单的方式在测试中创建假货和模拟。有时 RSpec allow(foo).to receive(:blah) 等可能会有点过于冗长,而使用 Struct 则要简单得多。

    【讨论】:

      【解决方案3】:

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

      对此进行了很好的讨论

      【讨论】:

      • 免责声明:我是RubyTapas的编辑;我在研究该文章中的链接时发现了这个 Stack Overflow 问题。
      • 欢迎来到 Stack Overflow!虽然这在理论上可以回答问题,it would be preferable 在此处包含答案的基本部分,并提供链接以供参考。
      • 额外免责声明:我是 RubyTapas 的主厨,这个答案是在我传达(或者实际上是制定)关于发布链接到 RubyTapas 的答案的政策之前发布的。我认为我们都没有能力直接删除它,但如果它违反 SO 社区标准,我们可以将其删除。
      • @Avdi 不需要删除,只需修改以包含要点。任何发帖者都应该能够删除自己的帖子。
      【解决方案4】:

      我想@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)
      

      【讨论】:

        【解决方案5】:

        实际性能差异很大,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
        

        【讨论】:

          猜你喜欢
          • 2013-09-17
          • 2011-07-26
          • 2012-06-16
          • 1970-01-01
          • 1970-01-01
          • 2011-05-13
          • 2011-02-14
          相关资源
          最近更新 更多