如何最好地分解出常见的 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::inheritedEntity 上执行,传递为已创建的参数 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).