为构造函数外部的实例变量指定默认值
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为构造函数外部的实例变量指定默认值相关的知识,希望对你有一定的参考价值。
我的目标是在不使用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吗?有没有办法在不牺牲运行时或代码优雅的情况下做到这一点?
你可以做的一件事是从initialize
的Animal
调用一个方法,为子类提供一个钩子来添加自定义功能:
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
默认传递原始参数。现在,如果你回去改变动物,比如说它的name
有initialize
参数:
class Animal
attr_reader :age, :name
def initialize(name, age)
@name = name
@age = age
end
end
Sheep
仍然没有任何变化。
在最后一个例子中:
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
有了补丁你可以做:
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
以上是关于为构造函数外部的实例变量指定默认值的主要内容,如果未能解决你的问题,请参考以下文章