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。