Ruby – 初始化对象时设置属性值

鉴于以下课程:

class Test attr_accessor :name end 

当我创建对象时,我想执行以下操作:

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

目前它的名字仍然是nil 。 这可能吗?

注意:我不想添加初始化程序。

好,

我想出了一个解决方案。 它使用初始化方法但另一方面完全按照您的要求进行操作。

 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 

开心吗? 🙂


使用inheritance的替代解决方案。 注意,使用此解决方案,您无需显式声明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}" 

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

 class Test < Struct.new(:name) end 

所以现在:

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

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

继续你的榜样

 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' # other initialization, if needed end 

请注意,这种方式不需要复杂的initialize方法(实际上是单行)。

如前所述,执行此操作的明智方法是使用Struct或定义Test#initialize方法。 这正是结构和构造函数的用途。 使用与属性对应的选项哈希是最接近的C#示例,它是一个看似普通的Ruby约定:

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

但是在你的例子中,你做的事情看起来更像是使用=变量赋值,所以让我们尝试使用块而不是哈希。 (你也使用了在Ruby中常量的Name ,我们会改变它。)

 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" } # => # t.name # => "something" 

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

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

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

希望这是一个非常具体和有趣的请求的创造性解决方案。

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

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

需要子类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

你可以做到这一点。

 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之外创建它们。

如果您不想覆盖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。 如果你传递了其他内容,将使用这些参数调用initialize。