【问题标题】:How to Best Factor Out Common Class Methods如何最好地分解常见的类方法
【发布时间】:2020-05-31 02:23:20
【问题描述】:

我正在用 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 数组移动到超类会导致数组对 所有 类通用,这不是我需要的。

【问题讨论】:

    标签: ruby oop class-method


    【解决方案1】:

    你需要做的主要改变是使用类实例变量而不是类变量。出于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
    

    我在Entity单例类1中为类实例变量@ins定义了一个getter

    class << self
      attr_reader :ins
    end
    

    当创建Entity 的子类时,回调方法Class::inheritedEntity 上执行,并将已创建的类作为参数传递。 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 等于nil,则将@ins 设置为空数组。如果在创建之前引用了@ins,则返回nil,因此无论哪种方式,@ins 都设置为等于[]。为了执行此语句,我需要将 attr_reader :ins 更改为 attr_accessor :ins 以执行分配 @ins = [](尽管我可以使用 instance_variable_set 代替)。

    请注意,如果我要将行 @ins = [] 添加到 Entity(例如第一行),则在创建子类时将为每个子类创建实例变量 @ins,但该实例变量不会被初始化为空数组,因此该行将毫无用处。

    1.或者,可以写,singleton_class.public_send(:attr_reader, :ins)

    【讨论】:

      猜你喜欢
      • 2021-10-10
      • 1970-01-01
      • 2020-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多