我正在用Ruby构建内存中的实例模型。有很多类,每个类都由该类上的类方法实例化和管理。有很多这样的类方法,例如列出所有实例,检索所有实例,等等。
这些方法的代码在所有类中都是通用的,不需要考虑这些类的任何特殊性。因此,我希望该代码可以存在于一个共同的地方。请参见下面的list
方法。我的问题:如何最好地实现这一目标。
class A
attr_reader :value
@@instances = []
def initialize(value:)
@value = value; @@instances << self
end
def self.list
@@instances.each { |i| puts "#{i.value}"}
end
end
class B
attr_reader :value
@@instances = []
def initialize(value:)
@value = value; @@instances << self
end
def self.list
@@instances.each { |i| puts "#{i.value}"}
end
end
A.new(value: '100')
A.new(value: '101')
B.new(value: '200')
B.new(value: '201')
A.list
B.list
理想情况下,我只定义一次list方法。我也尝试将其移至超类:
class Entity
def self.list
@@instances.each { |i| puts "AB: #{i.value}"}
end
end
class A < Entity
attr_reader :value
@@instances = []
def initialize(value:)
@value = value; @@instances << self
end
end
class B < Entity
attr_reader :value
@@instances = []
def initialize(value:)
@value = value; @@instances << self
end
end
...但是,正如人们期望的那样,超类无法访问其子类的@@instances
数组。将@@instances
数组移到超类会导致该数组对于all类是通用的,这不是我所需要的。
您需要进行的主要更改是使用类实例变量而不是类变量。出于解释的原因,应谨慎使用here类变量;类实例变量通常是一个更好的选择,这个问题很好地说明了这一点。
class Entity
attr_reader :value
class << self
attr_reader :ins
end
def self.inherited(klass)
klass.instance_variable_set(:@ins, [])
end
def initialize(value:)
@value = value
self.class.ins << self
end
def self.list
@ins.each { |i| puts "#{i.value}"}
end
end
class A < Entity; end
class B < Entity; end
A.new(value: '100')
#=> #<A:0x00005754a59dc640 @value="100">
A.new(value: '101')
#=> #<A:0x00005754a59e4818 @value="101">
A.list
# 100
# 101
B.new(value: '200')
#=> #<B:0x00005754a59f0910 @value="200">
B.new(value: '201')
#=> #<B:0x00005754a59f8b88 @value="201">
B.list
# 200
# 201
我为@ins
的单例类 1:中的类实例变量Entity
定义了getter
class << self
attr_reader :ins
end
创建Entity
的子类时,回调方法 Class::inherited在Entity
上执行,将已创建的类作为参数传递。 inherited
为创建的类创建并初始化(为空数组)类实例变量@ins
。
不使用回调方法的另一种方法如下。
class Entity
attr_reader :value
class << self
attr_accessor :ins
end
def initialize(value:)
@value = value
(self.class.ins ||= []) << self
end
def self.list
@ins.each { |i| puts "#{i.value}"}
end
end
片段:
(self.class.ins ||= [])
如果@ins
等于@ins
,则将nil
设置为空数组。如果在创建@ins
之前已对其进行引用,则返回nil
,因此无论哪种方式,@ins
都将等于[]
。为了执行此语句,我需要将attr_reader :ins
更改为attr_accessor :ins
以执行分配@ins = []
(尽管我可以改用instance_variable_set
)。
[请注意,如果我将@ins = []
行添加到Entity
(例如第一行),则在创建子类时将为每个子类创建实例变量@ins
,但是该实例变量将没有初始化为空数组,因此该行将无用。
1。或者,可以写singleton_class.public_send(:attr_reader, :ins)
。