如何最好地分解出常见的 Class 方法
How to Best Factor Out Common Class Methods
我正在 Ruby 中构建内存中实例模型。有一堆 classes,每个都由 class 上的 class 方法实例化和管理。有很多 class 方法,例如列出所有实例、检索所有实例等
这些方法的代码在所有 classes 中都是通用的,不需要考虑这些 classes 的任何特殊性。因此,我希望该代码位于一个公共位置。请参阅下面的 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
理想情况下,我只定义一次列表方法。我也试过把它移到超级class:
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
...但正如人们所期望的那样,超级class 无法访问其子class 的@@instances
数组。将 @@instances
数组移动到 super-class 导致数组对 all classes 通用,这不是我需要的。
您需要做的主要更改是使用 class 实例变量 而不是 class 变量。出于解释的原因 here class 应谨慎使用变量; class 实例变量通常是更好的选择,这个问题很好地说明了这一点。
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
我在Entity
的中为class实例变量@ins
定义了一个getter单例 class1:
class << self
attr_reader :ins
end
当创建 Entity
的子 class 时, 回调方法 Class::inherited 在 Entity
上执行,传递为已创建的参数 class。 inherited
创建并初始化(到一个空数组) class 实例变量 @ins
为 class 创建。
另一种不使用回调方法的方法如下。
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
等于 nil
, 将 @ins
设置为空数组。如果 @ins
在创建之前被引用,则返回 nil
,因此无论哪种方式,@ins
都设置为等于 []
。为了执行此语句,我需要将 attr_reader :ins
更改为 attr_accessor :ins
以执行赋值 @ins = []
(尽管我可以改用 instance_variable_set
)。
请注意,如果我要将行 @ins = []
添加到 Entity
(例如,作为第一行),将为每个子 [= 创建实例变量 @ins
80=] 当 subclass 创建时,但该实例变量不会被初始化为一个空数组,因此该行将没有任何作用。
1.或者,可以写 singleton_class.public_send(:attr_reader, :ins)
.
我正在 Ruby 中构建内存中实例模型。有一堆 classes,每个都由 class 上的 class 方法实例化和管理。有很多 class 方法,例如列出所有实例、检索所有实例等
这些方法的代码在所有 classes 中都是通用的,不需要考虑这些 classes 的任何特殊性。因此,我希望该代码位于一个公共位置。请参阅下面的 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
理想情况下,我只定义一次列表方法。我也试过把它移到超级class:
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
...但正如人们所期望的那样,超级class 无法访问其子class 的@@instances
数组。将 @@instances
数组移动到 super-class 导致数组对 all classes 通用,这不是我需要的。
您需要做的主要更改是使用 class 实例变量 而不是 class 变量。出于解释的原因 here class 应谨慎使用变量; class 实例变量通常是更好的选择,这个问题很好地说明了这一点。
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
我在Entity
的中为class实例变量@ins
定义了一个getter单例 class1:
class << self
attr_reader :ins
end
当创建 Entity
的子 class 时, 回调方法 Class::inherited 在 Entity
上执行,传递为已创建的参数 class。 inherited
创建并初始化(到一个空数组) class 实例变量 @ins
为 class 创建。
另一种不使用回调方法的方法如下。
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
等于 nil
, 将 @ins
设置为空数组。如果 @ins
在创建之前被引用,则返回 nil
,因此无论哪种方式,@ins
都设置为等于 []
。为了执行此语句,我需要将 attr_reader :ins
更改为 attr_accessor :ins
以执行赋值 @ins = []
(尽管我可以改用 instance_variable_set
)。
请注意,如果我要将行 @ins = []
添加到 Entity
(例如,作为第一行),将为每个子 [= 创建实例变量 @ins
80=] 当 subclass 创建时,但该实例变量不会被初始化为一个空数组,因此该行将没有任何作用。
1.或者,可以写 singleton_class.public_send(:attr_reader, :ins)
.