【问题标题】:How to change the default value of a Struct attribute?如何更改 Struct 属性的默认值?
【发布时间】:2013-02-24 19:46:41
【问题描述】:

根据the documentation未设置的Struct属性设置为nil

未设置的参数默认为零。

是否可以为特定属性指定默认值?

例如,对于下面的 Struct

Struct.new("Person", :name, :happy)

我希望属性 happy 默认为 true 而不是 nil。我怎样才能做到这一点?如果我这样做

Struct.new("Person", :name, :happy = true)

我明白了

-:1: syntax error, unexpected '=', expecting ')'
Struct.new("Person", :name, :happy = true)
                                    ^
-:1: warning: possibly useless use of true in void context

【问题讨论】:

  • 如果不定义新类,我认为这是不可能的。
  • @Linuxios 至少可以用猴子补丁来实现吗?
  • 你为什么不考虑OpenStruct来实现你的目标?
  • @Passionate 如果可以通过OpenStruct 实现,那可能是有用的答案。
  • 检查我的答案@N.N.

标签: ruby


【解决方案1】:

这也可以通过将 Struct 创建为子类并覆盖 initialize 具有默认值,如下例所示:

class Person < Struct.new(:name, :happy)
    def initialize(name, happy=true); super end
end

一方面,这种方法确实会导致一些样板;另一方面,它可以简洁明了地满足您的需求。

一个副作用(根据您的偏好/用例可能是好处或烦恼)是您失去了默认为 nil 的所有属性的默认 Struct 行为 - 除非您明确设置它们是这样。实际上,上面的示例将使name 成为必需参数,除非您将其声明为name=nil

【讨论】:

  • 另外,当您将initialize 定义放在一行时,它会说“超级结束”,这感觉很有趣,让我很开心:D
  • 无需再添加图层类。您可以覆盖或隐藏初始化方法:Person = Struct.new(:name, :happy){ def initialize(name, happy=true); super; end }。此实现的唯一警告是,您必须为要提供默认值的参数之前的每个参数指定 nil,如果您的属性已经超过 5,则可能会很混乱。
  • 同意@konsolebox 关于这一点,另外是一种不好的做法rubydoc.info/gems/rubocop/0.29.1/RuboCop/Cop/Style/…
【解决方案2】:

按照@rintaun 的示例,您也可以在 Ruby 2+ 中使用关键字参数来做到这一点

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

A.new
# ArgumentError: missing keyword: a

A.new a: 1
# => #<struct A a=1, b=2, c=3> 

A.new a: 1, c: 6
# => #<struct A a=1, b=2, c=6>

更新

现在需要编写如下代码才能工作。

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

【讨论】:

  • 这在技术上是正确的,虽然也很简洁。请在下面以相同的确切方向查看我的答案,但使用更真实的示例。
  • 不,你不能。它实际上将所有关键字参数分配给第一个 a 变量(Ruby 2.3.1)所以在最后一种情况下你的游戏我得到了#&lt;struct A a={:a=&gt;1, :b=&gt;2, :c=&gt;6}, b=nil, c=nil&gt;
  • @goodniceweb 我已经更新了我的答案以使用更高版本的 Ruby。
【解决方案3】:

我还发现了这个:

Person = Struct.new "Person", :name, :happy do
  def initialize(*)
    super
    self.location ||= true
  end
end

【讨论】:

    【解决方案4】:

    @Linuxios 给出了覆盖成员查找的答案。这有几个问题:您不能将成员显式设置为 nil,并且每个成员引用都有额外的开销。在我看来,您真的只想在使用提供给::new::[] 的部分成员值初始化新结构对象时提供默认值。

    这是一个扩展 Struct 的模块,它带有一个额外的工厂方法,可让您使用散列描述所需的结构,其中键是成员名称,而值是在初始化时未提供时填充的默认值:

    # Extend stdlib Struct with a factory method Struct::with_defaults
    # to allow StructClasses to be defined so omitted members of new structs
    # are initialized to a default instead of nil
    module StructWithDefaults
    
      # makes a new StructClass specified by spec hash.
      # keys are member names, values are defaults when not supplied to new
      #
      # examples:
      # MyStruct = Struct.with_defaults( a: 1, b: 2, c: 'xyz' )
      # MyStruct.new       #=> #<struct MyStruct a=1, b=2, c="xyz"
      # MyStruct.new(99)   #=> #<struct MyStruct a=99, b=2, c="xyz">
      # MyStruct[-10, 3.5] #=> #<struct MyStruct a=-10, b=3.5, c="xyz">
      def with_defaults(*spec)
        new_args = []
        new_args << spec.shift if spec.size > 1
        spec = spec.first
        raise ArgumentError, "expected Hash, got #{spec.class}" unless spec.is_a? Hash
        new_args.concat spec.keys
    
        new(*new_args) do
    
          class << self
            attr_reader :defaults
          end
    
          def initialize(*args)
            super
            self.class.defaults.drop(args.size).each {|k,v| self[k] = v }
          end
    
        end.tap {|s| s.instance_variable_set(:@defaults, spec.dup.freeze) }
    
      end
    
    end
    
    Struct.extend StructWithDefaults
    

    【讨论】:

      【解决方案5】:

      只需添加另一个变体:

      class Result < Struct.new(:success, :errors)
        def initialize(*)
          super
          self.errors ||= []
        end
      end
      

      【讨论】:

        【解决方案6】:

        我认为覆盖#initialize 方法是最好的方法,调用#super(*required_args)

        这还有一个额外的优势,那就是能够使用散列样式的参数。请看下面的完整编译示例:

        哈希样式参数、默认值和 Ruby 结构

        # This example demonstrates how to create Ruby Structs that use
        # newer hash-style parameters, as well as the default values for
        # some of the parameters, without loosing the benefits of struct's
        # implementation of #eql? #hash, #to_s, #inspect, and other
        # useful instance methods.
        #
        # Run this file as follows
        #
        # > gem install rspec
        # > rspec struct_optional_arguments.rb --format documentation
        #
        class StructWithOptionals < Struct.new(
            :encrypted_data,
            :cipher_name,
            :iv,
            :salt,
            :version
            )
        
            VERSION = '1.0.1'
        
            def initialize(
                encrypted_data:,
                cipher_name:,
                iv: nil,
                salt: 'salty',
                version: VERSION
                )
                super(encrypted_data, cipher_name, iv, salt, version)
            end
        end
        
        require 'rspec'
        RSpec.describe StructWithOptionals do
            let(:struct) { StructWithOptionals.new(encrypted_data: 'data', cipher_name: 'AES-256-CBC', iv: 'intravenous') }
        
            it 'should be initialized with default values' do
                expect(struct.version).to be(StructWithOptionals::VERSION)
            end
        
            context 'all fields must be not null' do
                %i(encrypted_data cipher_name salt iv version).each do |field|
                    subject { struct.send(field) }
                    it field do
                        expect(subject).to_not be_nil
                    end
                end
            end
        end
        

        【讨论】:

        • 您的回答没有添加任何内容。请参阅@rintaun 的回答。也没有必要使用另一个类,因为 Struct.new 的乘积实际上只是一个空类,其中超类是 Struct。
        • 我添加的主要内容是新的哈希样式参数参数。我发现它们更容易阅读和使用。在我的示例中,您可以拥有一个接收散列参数的结构,其中一些具有默认值。因此,这是该答案的特定情况。当然没有必要使用另一个类name。通过该论点,不必使用命名变量或函数。为什么不直接从 Object 派生一切?
        • 好吧,我错过了。如果有人更愿意通过关键字参数将值传递给初始化方法,那将是一个解决方案。
        • 我不是在谈论你是否会使用另一个类 name。我说的是是否有必要从另一个匿名类派生。我没有看到您关于从 Object 派生的所有内容的 夸大 观点。从 Struct.new 创建的类实际上是一个空类。它甚至没有初始化功能。它只有属性读取器/写入器。中间有一个空类是没有意义的,它是not recommended
        • 好的,感谢您的参考。我实际上不知道这两种方法在幕后实际上是不同的。迷人!所以考虑了一下这一点,我想知道我是否以不同的方式使用结构。我主要将它们用作创建依赖于几个必需属性的类的简写。所以它就像类固醇上的 attr_accessor。我喜欢我创建了一个可重用的类,并将结构视为实现细节。您如何看待这种用法,@konsolebox?
        猜你喜欢
        • 2014-01-13
        • 1970-01-01
        • 2021-01-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-04
        • 1970-01-01
        • 2018-11-03
        相关资源
        最近更新 更多