Ruby 为构造函数外部的实例变量指定默认值

Ruby 为构造函数外部的实例变量指定默认值,ruby,inheritance,instance-variables,Ruby,Inheritance,Instance Variables,我的目标是在不使用initialize方法的情况下初始化实例变量。 我有以下代码: class Animal attr_reader :age def initialize(age) @age = age end end class Sheep < Animal attr_accessor :likes def initialize(age) super @likes = [] end end sheep = Sheep.new(5) s

我的目标是在不使用initialize方法的情况下初始化实例变量。 我有以下代码:

class Animal
  attr_reader :age
  def initialize(age)
    @age = age
  end
end

class Sheep < Animal
  attr_accessor :likes
  def initialize(age)
    super
    @likes = []
  end
end

sheep = Sheep.new(5)
sheep.likes << "grass"
这要优雅得多,因为不需要重新调整签名,但它仍然不完美:当我访问该实例变量时,Ruby不会检查
likes
的非
nil
-ness吗?有没有一种方法可以在不牺牲运行时或代码优雅性的情况下做到这一点?

在最后一个示例中:

class Sheep < Animal
  attr_accessor :likes

  def likes
    @likes || @likes = []
  end
end
此外,由于您现在将
likes
作为一种记忆方法,而不是实例的属性,因此不需要
attr\u访问器(或
attr\u reader
),等等


您可以做的一件事是从
Animal
initialize
调用一个方法,为子类提供一个钩子来添加自定义功能:

class Animal
  attr_reader :age
  def initialize(age)
    @age = age

    setup_defaults
  end

  private
  def setup_defaults
    # NOOP by default
  end
end

class Sheep < Animal
  attr_accessor :likes

  private
  def setup_defaults
    @likes = []
  end
end
作为第三个选项,如果您不介意使用
initialize
(您主要关心的可能是更改超类的签名),因为您不关心初始化
Sheep
的任何参数,您可以覆盖
initialize
,如下所示:

class Sheep < Animal
  attr_accessor :likes
  def initialize(*)
    super
    @likes = []
  end
end

Sheep
仍然可以正常工作,没有任何变化。

使用实验补丁,您可以执行以下操作:

class Zaloop

  attr_accessor var1: :default_value, var2: 2

  def initialize
    self.initialize_default_values
  end

end

puts Zaloop.new.var1 # :default_value
  • 请注意,下面的补丁是实验性的解决方案,如果您决定在生产中使用它,请小心
模块的修补程序:

Module.module_eval do

  alias _original_attr_accessor attr_accessor
  def attr_accessor(*args)
    @default_values ||= {}
    attr_names = []
    args.map do |arg|
      if arg.is_a? Hash
        arg.each do |key, value|
          define_default_initializer if @default_values.empty?
          @default_values[key] = value
          attr_names << key
        end
      else
        attr_names << arg
      end
    end
    _original_attr_accessor *attr_names
  end

  def define_default_initializer
    default_values = @default_values
    self.send :define_method, :initialize_default_values do
      default_values.each do |key, value|
        instance_variable_set("@#{key}".to_sym, value)
      end
    end
  end

end
Module.Module\u eval do
别名\u原始\u属性\u访问器属性\u访问器
def属性存取器(*args)
@默认_值| |={}
属性名称=[]
args.map do | arg|
如果arg.is_a?搞砸
arg.每个do |键、值|
如果@default\u values.empty,定义\u default\u初始值设定项?
@默认值[键]=值

attr_名称真正的问题不是实例变量与类实例变量不同。即使您在类主体中初始化了类变量或常量,在实例方法中使用它(将元素推入)也意味着您正在修改来自不同对象的共享数组。“这似乎没有道理。”sawa我可以问一下这个评论指的是什么吗?我知道在类主体中初始化类变量会给我一种不同的行为,这就是我所说的“其他OO语言可以做到的”。我主要是想用Ruby寻找一个优雅的解决方案,它允许我基本上了解Java中成员变量的行为,而不必担心以后重写代码。@sawa顺便说一句,我自由地重新添加了您所做编辑的一些短语。我同意我的问题太长了,但我觉得这个问题背后的动机有些模糊。(而且,这听起来可能像是我在向Ruby开枪。我实际上是想向我的代码开枪。哎呀)。请不要在生产中使用它。从未。
class Animal
  attr_reader :age
  def initialize(age)
    @age = age

    setup_defaults
  end

  private
  def setup_defaults
    # NOOP by default
  end
end

class Sheep < Animal
  attr_accessor :likes

  private
  def setup_defaults
    @likes = []
  end
end
def likes
  @likes ||= [] # shorter way of doing what you have
end
class Sheep < Animal
  attr_accessor :likes
  def initialize(*)
    super
    @likes = []
  end
end
class Animal
  attr_reader :age, :name
  def initialize(name, age)
    @name = name
    @age = age
  end
end
class Zaloop

  attr_accessor var1: :default_value, var2: 2

  def initialize
    self.initialize_default_values
  end

end

puts Zaloop.new.var1 # :default_value
Module.module_eval do

  alias _original_attr_accessor attr_accessor
  def attr_accessor(*args)
    @default_values ||= {}
    attr_names = []
    args.map do |arg|
      if arg.is_a? Hash
        arg.each do |key, value|
          define_default_initializer if @default_values.empty?
          @default_values[key] = value
          attr_names << key
        end
      else
        attr_names << arg
      end
    end
    _original_attr_accessor *attr_names
  end

  def define_default_initializer
    default_values = @default_values
    self.send :define_method, :initialize_default_values do
      default_values.each do |key, value|
        instance_variable_set("@#{key}".to_sym, value)
      end
    end
  end

end