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

问题描述 投票:0回答:3

我的目标是在不使用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"

这个子类中的initialize方法调用super。这不能很好地扩展:如果我改变了超类的签名,我也必须在所有子类中进行调整。

如果我可以在@likes = []的类范围内的initialize方法之外初始化像Sheep这样的实例变量会更好,就像许多其他OO语言一样。但是,这会使我的变量成为类对象的实例变量。

这是我发现的一种不会覆盖构造函数的方法:

class Sheep < Animal
  attr_accessor :likes
  def likes
    @likes || @likes = []
  end
end

这更加优雅,因为重新调整签名不是必需的,但它仍然不完美:当我访问该实例变量时,Ruby不会检查nil的非likes-ness吗?有没有办法在不牺牲运行时或代码优雅的情况下做到这一点?

ruby inheritance instance-variables
3个回答
1
投票

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

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而不是attr_reader / attr_accessor

def likes
  @likes ||= [] # shorter way of doing what you have
end

作为第三种选择,如果你不介意使用initialize(你的主要关注似乎可能会改变超类的签名),因为你不关心任何初始化Sheep的参数是你可以覆盖initialize像:

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

这与像def initialize(*args)这样的东西相同,除了你没有命名变量,并且因为super默认传递原始参数。现在,如果你回去改变动物,比如说它的nameinitialize参数:

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

Sheep仍然没有任何变化。


2
投票

在最后一个例子中:

class Sheep < Animal
  attr_accessor :likes

  def likes
    @likes || @likes = []
  end
end

你基本上使用的是memoization,虽然你的语法与规范有点不同,它看起来像:

def likes
  @likes ||= []
end

另外,因为你现在有likes作为memoized方法,而不是实例的属性,你不需要attr_accessor(或attr_reader等)。

class Sheep < Animal
  def likes
    @likes ||= []
  end
end

你很高兴去。

编辑:根据您对性能的关注:

[1] pry(main)> require 'benchmark'
=> true
[2] pry(main)> @hello = []
=> []
[3] pry(main)> def hello
[3] pry(main)*   @hello
[3] pry(main)* end
=> :hello
[4] pry(main)> def likes
[4] pry(main)*   @likes ||= []
[4] pry(main)* end
=> :likes
[5] pry(main)> puts Benchmark.measure { 1_000_000.times { hello } }
  0.070000   0.000000   0.070000 (  0.071330)
=> nil
[6] pry(main)> puts Benchmark.measure { 1_000_000.times { likes } }
  0.100000   0.000000   0.100000 (  0.097388)
=> nil 

0
投票

有了补丁你可以做:

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
© www.soinside.com 2019 - 2024. All rights reserved.