如何动态地为类创建attr_reader

时间:2019-02-04 22:36:49

标签: ruby

如何在对象上动态创建属性并将对象分配为其值? 我有以下代码:

class Component
  def initialize
  end    
end

class BaseClass
  def initialize
  end

  # define class methods
  def self.create_component(**args)
    # create attr_accessor with name "ball"
    # set ball = Component.new
  end
end

class ChildClass < BaseClass

  create_component :name => "ball"
  create_component :name => "baseball"
  def initialize
  end
end

我的目标是,当ChildClass调用“ create_component”方法时,这应创建一个具有:name参数中提供的名称的属性,并将该属性实例化为“ Component”对象。

object = ChildClass.new
object.ball #=> to return object reference (Component class 1)
object.baseball #=> to return object reference (Component class 2)

3 个答案:

答案 0 :(得分:3)

您可以这样定义BaseClass:

class Component; end

class BaseClass
  @@components = []
  def initialize
    @@components.each do |attr|
      instance_variable_set("@#{attr}", Component.new)
    end
  end
  def self.create_component(name:)
    attr_reader name.to_sym
    @@components << name
  end
end

class ChildClass < BaseClass
  create_component :name => "ball"
  create_component :name => "baseball"
end

object = ChildClass.new

object.ball
# => #<Component:0x00007fa62fbdf3f8>

object.baseball
# => #<Component:0x00007fa62fbdf3a8>

基本上,您要做三件事:

  1. 创建一个类级实例变量@@components,该变量存储自定义attr_reader名称的列表
  2. create_component内,调用attr_reader创建方法并将其添加到@@components列表中
  3. initialize内,为每个@@components设置初始值(如果愿意,您也可以使用initialize自变量为其提供自定义值)。

答案 1 :(得分:0)

您可以按照以下步骤进行操作。

class Component
end

class BaseClass
  def create_component(name)
    self.class.send(:attr_accessor, name)
    instance_variable_set("@#{name}", Component.new)
  end
end

class ChildClass < BaseClass
end

c = ChildClass.new
c.create_component "ball"
  #=> #<Component:0x000056f36c73c2e0> 
c.create_component"baseball"
  #=> #<Component:0x000056f36c7533f0>
c.methods & [:ball, :baseball]
  #=> [:ball, :baseball] 
c.instance_variables
  #=> [:@ball, :@baseball] 
c.ball
  #=> #<Component:0x000056f36c73c2e0> 
c.baseball
  #=> #<Component:0x00005602be1eb560> 
c.ball = 1
c.ball
  #=> 1 

请注意

self.class.send(:attr_accessor, name)

可以替换为

self.class.class_eval { attr_accessor name.to_sym }

将实例变量添加到BaseClass的子类的实例无关紧要。如果将它们添加到BaseClass的实例中,基本上是相同的问题。

答案 2 :(得分:0)

这将创建一个实例方法,例如:

def baseball
  @baseball ||= Component.new
end

对于baseball=,它仅使用常规的attr_writer

class Component
end

class BaseClass
  def self.create_component(name:)
    class_eval <<~EOB
      attr_writer :#{name}
      def #{name}
        @#{name} ||= Component.new
      end
    EOB
  end
end

class ChildClass < BaseClass
  create_component name: "ball"
  create_component name: "baseball"
end

这似乎可行:

> c = ChildClass.new
> c.ball
=> #<Component:0x00007f8b270cfa58>
> c.baseball
=> #<Component:0x00007f8b270dbdd0>