【问题标题】:How can one set property values when initializing an object in Ruby?在 Ruby 中初始化对象时如何设置属性值?
【发布时间】:2010-02-27 19:23:53
【问题描述】:

给定以下类:

class Test
  attr_accessor :name
end

当我创建对象时,我想做以下事情:

t = Test.new {name = 'Some Test Object'}

目前,它导致name 属性仍然是nil

如果不添加初始化程序,这可能吗?

【问题讨论】:

  • 排除初始化方法(这就是您的意思吗?)使事情变得困难。在创建新的Test 对象时,name 是否应该每次都进行相同的初始化?还是应该获取开放代码中指定的值?
  • 动机是做一些类似于 C# - msdn.microsoft.com/en-us/library/bb384062.aspx
  • 您对我提出的解决方案有什么不满意的地方吗?
  • 不,我喜欢它,但您需要在实际类中添加一个初始化步骤。如果您查看 C# 是如何做到的,那么它是一种语言功能,适用于任何 C# 类。如果我已经有一个初始化步骤会怎样?如果它来自外部库怎么办?
  • @Ben 你有没有找到解决方案?我也喜欢 C# 对象初始化器

标签: ruby


【解决方案1】:

好的,

我想出了一个解决方案。它使用初始化方法,但另一方面却完全按照您的意愿行事。

class Test
  attr_accessor :name

  def initialize(init)
    init.each_pair do |key, val|
      instance_variable_set('@' + key.to_s, val)
    end
  end

  def display
    puts @name
  end

end

t = Test.new :name => 'hello'
t.display

开心吗? :)


使用继承的替代解决方案。请注意,使用此解决方案,您无需显式声明 attr_accessor!

class CSharpStyle
  def initialize(init)
    init.each_pair do |key, val|
      instance_variable_set('@' + key.to_s, val)
      instance_eval "class << self; attr_accessor :#{key.to_s}; end"
    end
  end
end

class Test < CSharpStyle
  def initialize(arg1, arg2, *init)
    super(init.last)
  end
end

t = Test.new 'a val 1', 'a val 2', {:left => 'gauche', :right => 'droite'}
puts "#{t.left} <=> #{t.right}"

【讨论】:

    【解决方案2】:

    正如其他人所提到的,最简单的方法是定义一个initialize 方法。如果你不想这样做,你可以让你的类继承自Struct

    class Test < Struct.new(:name)
    end
    

    那么现在:

    >> t = Test.new("Some Test Object")
    => #<struct Test name="Some Test Object">
    >> t.name
    => "Some Test Object"
    

    【讨论】:

      【解决方案3】:

      有一种进行复杂对象初始化的通用方法 通过必要的操作传递一个块。该块在 要初始化的对象的上下文,因此您可以轻松访问 所有实例变量和方法。

      继续你的例子,我们可以定义这个通用初始化器:

      class Test
        attr_accessor :name
      
        def initialize(&block)
          instance_eval(&block)
        end 
      end
      

      然后将相应的代码块传递给它:

      t = Test.new { @name = 'name' }
      

      t = Test.new do
        self.name = 'name'
        # Any other initialization code, if needed.
      end
      

      请注意,这种方法不需要增加太多复杂性 initialize 方法本身。

      【讨论】:

        【解决方案4】:

        如前所述,明智的做法是使用Struct 或定义Test#initialize 方法。这正是结构和构造函数的用途。使用与属性相对应的选项哈希与您的 C# 示例最接近,它是一个看起来很正常的 Ruby 约定:

        t = Test.new({:name => "something"})
        t = Test.new(name: "something") # json-style or kwargs
        

        但在您的示例中,您正在做的事情看起来更像是使用= 进行的变量赋值,所以让我们尝试使用块而不是哈希。 (您还使用了 Name,这在 Ruby 中是一个常量,我们将对其进行更改。)

        t = Test.new { @name = "something" }
        

        酷,现在让我们让它真正起作用:

        class BlockInit
          def self.new(&block)
            super.tap { |obj| obj.instance_eval &block }
          end
        end
        
        class Test < BlockInit
          attr_accessor :name
        end
        
        t = Test.new { @name = "something" }
        # => #<Test:0x007f90d38bacc0 @name="something">
        t.name
        # => "something"
        

        我们创建了一个带有构造函数的类,该构造函数接受一个块参数,该参数在新实例化的对象中执行。

        因为您说您想避免使用initialize,所以我改为覆盖new 并调用super 以从Object#new 获取默认行为。 通常我们会改为定义initialize,除非满足您问题中的特定要求,否则不建议使用此方法。

        当我们将块传递给BlockInit 的子类时,我们可以做的不仅仅是设置变量……我们实际上只是将代码注入initialize 方法(我们避免编写)。如果您还想要一个 initialize 方法来执行其他操作(正如您在 cmets 中提到的),您可以将其添加到 Test 甚至不必调用 super(因为我们的更改不在 BlockInit#initialize 中,而是BlockInit.new)

        希望这是针对非常具体和有趣的要求的创造性解决方案。

        【讨论】:

          【解决方案5】:

          您指示的代码是将参数传递给initialize 函数。你肯定不得不使用initialize,或者使用更无聊的语法:

          test = Test.new
          test.name = 'Some test object'
          

          【讨论】:

            【解决方案6】:

            需要子类化 Test(这里显示了自己的方法和初始化程序)例如:

            class Test
              attr_accessor :name, :some_var
            
              def initialize some_var
                @some_var = some_var
              end
            
              def some_function
                "#{some_var} calculation by #{name}"
              end
            end
            
            class SubClassedTest < Test
              def initialize some_var, attrbs
                attrbs.each_pair do |k,v|
                  instance_variable_set('@' + k.to_s, v)
                end
                super(some_var)
              end
            end
            
            tester = SubClassedTest.new "some", name: "james"
            puts tester.some_function
            

            输出:some calculation by james

            【讨论】:

              【解决方案7】:

              你可以这样做。

              class Test
                 def not_called_initialize(but_act_like_one)
                      but_act_like_one.each_pair do |variable,value|
                          instance_variable_set('@' + variable.to_s, value)
                          class << self
                                  self
                          end.class_eval do
                                  attr_accessor variable
                          end
                      end
                 end
              end
              
              (t = Test.new).not_called_initialize :name => "Ashish", :age => 33
              puts t.name #=> Ashish
              puts t.age  #=> 33
              

              一个优点是您甚至不必使用attr_accessor 预先定义实例变量。您可以通过not_called_initialize 方法传递您需要的所有实例变量,并让它在定义getter 和setter 之外创建它们。

              【讨论】:

                【解决方案8】:

                如果您不想覆盖initialize,那么您必须向上移动并覆盖new。这是一个例子:

                class Foo
                  attr_accessor :bar, :baz
                
                  def self.new(*args, &block)
                    allocate.tap do |instance|
                      if args.last.is_a?(Hash)
                        args.last.each_pair do |k,v|
                          instance.send "#{k}=", v
                        end
                      else
                        instance.send :initialize, *args
                      end
                    end
                  end
                
                  def initialize(*args)
                    puts "initialize called with #{args}"
                  end
                end
                

                如果您传入的最后一件事是 Hash,它将绕过 initialize 并立即调用 setter。如果您传递任何其他内容,它将使用这些参数调用初始化。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2012-12-15
                  • 1970-01-01
                  • 2019-01-27
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-02-24
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多