Ruby Class vs Struct

后端 未结 5 817
滥情空心
滥情空心 2021-02-02 08:48

I have seen codebases using Structs to wrap around attributes and behavior inside a class. What is the difference between a Ruby Class and a Struct? And when should one be used

相关标签:
5条回答
  • 2021-02-02 09:19

    To add to the other answers, there are some things you can not do with a Struct, and some than you can.

    For example, you can not create a Struct with no arguments:

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

    However, a class will let you do that:

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

    You can not set a default value for Struct arguments:

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

    But you can do it with a class, either passing a hash, were the arguments can be in any order or even missing:

    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'
    

    or passing the values directly, were the arguments should be given in the same order, with the defaulted ones always at the end:

    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'
    

    You can not do any of that with Structs, unless you set default values for your arguments in this super verbose way:

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

    (example taken from this answer)

    You can define methods in a Struct:

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

    Structs can be useful to create data objects, like the point example mentioned in one of the answers.

    I sometimes use them to create fakes and mocks in tests in a simple way. Sometimes RSpec allow(foo).to receive(:blah) etc. can get a bit too verbose and using a Struct is much simple.

    0 讨论(0)
  • 2021-02-02 09:25

    Struct is a Ruby shorthand for creating Classes. Using Struct where applicable simplifies your code. There is a good discussion of this at https://www.rubytapas.com/2012/11/07/episode-020-struct/

    0 讨论(0)
  • From the Struct docs:

    A Struct is a convenient way to bundle a number of attributes together, using accessor methods, without having to write an explicit class.

    The Struct class generates new subclasses that hold a set of members and their values. For each member a reader and writer method is created similar to Module#attr_accessor.

    So, if I want a Person class that I can access a name attribute (read and write), I either do it by declaring a class:

    class Person
      attr_accessor :name
    
      def initalize(name)
        @name = name
      end
    end
    

    or using Struct:

    Person = Struct.new(:name)
    

    In both cases I can run the following code:

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

    When use it?

    As the description states we use Structs when we need a group of accessible attributes without having to write an explicit class.

    For example I want a point variable to hold X and Y values:

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

    Some more examples:

    • http://blog.steveklabnik.com/posts/2012-09-01-random-ruby-tricks--struct-new
    • "When to use Struct instead of Hash in Ruby?" also has some very good points (comparing to the use of hash).
    0 讨论(0)
  • 2021-02-02 09:34

    I'd like to to @sam_forgot suggested benchmark. The comparison is not very fair. Both class and struct, these days, support keyword arguments. Using keyword arguments on each has opposite effects, as you can see from my example struct's with keyword arguments performance is not that dramatically different from the class.

    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 讨论(0)
  • 2021-02-02 09:39

    There is quite a big practical performance difference, example behavior in 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)
    

    Example code:

    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 讨论(0)
提交回复
热议问题