Ruby - 子类对象能否从父对象接收和修改实例变量

Ruby - Can a subclass object receive and modify instance variables from a parent object

我有一个系统,用户可以在其中创建 class Map 的新对象,该对象具有 SubMap 的子class。问题是,可以有多个 Map 对象,并且对于每个 Map 对象,可以有多个 SubMap 对象。我需要能够创建一个 SubMap 对象,该对象既可以获取也可以更改在 SubMap 实例化期间传递的唯一 ID 的 Map 对象的 @b 值。 例如:

map1=Map.new(10,5) #Map is assigned unique ID of 1 and has a few instance variables (@b = 5)
point=Map::SubMap.new(1) #Assigned to the map object with an ID of 1, which is map1
point.change_b(8) #Change map1's @b value to 8.

这是一个扩展的代码示例:

class Map
@@id=1
def change_b(b)
    @b=b
end
def initialize(a,b)
    @id=@@id
    @@id+=1
    @a=a
    @b=b
    @map="#{a}_#{b}"
    end
end

class SubMap < Map
    def initialize(mapId)
        @mapID=mapId
    end
    def change_b(b)
        super
    end
end

map=Map.new(5,2) #Create New Map            ID: 1, @b = 2
second_map=Map.new(6,1) #Create Test Map    ID: 2, @b = 1
point=Map::SubMap.new(1) #Just affect the map with the ID of 1
point.change_b(5) #Change map's (Instance of Map with ID of 1) instance variable of @b to 5
##second_map's @b value is unchanged.

我欢迎使用任何其他方法来执行此操作(无双关语意),在此先感谢。另外,对于任何格式错误(缺少缩进以及我可能遗漏的错误),我深表歉意,我只说我和 SO 代码格式在一些事情上不一致。

我的(可能非常次优的)解决方案是获取 Map 的所有实例,找到 @id 等于子图的 @mapID 的实例。一旦我们有了它,我们将它的@b 设置为 b.

所以它的工作原理如下:

  1. ObjectSpace.each_object(Map)

它获取 Map 的所有实例的枚举器,您可以在此处找到更多信息:How do I list all objects created from a class in Ruby?

  1. Enumerator.select {|m| m.instance_variable_get(:@id) == @mapID}

从我们在第 1 步中获得的 Enumerator,我们 select 只有实例变量 @id 等于当前 subMap 的 @mapID 的实例。

  1. MapInstance.instance_variable_set(:@b, b)

既然我们已经有了@id 等于子地图的@mapID 的地图,那么我们只需要将这个地图的实例变量@b 设置为方法的参数b 即可。有关更多信息,请参见此处:How to set private instance variable used within a method test?

将它们放在一起我们得到:

class Map
  @@id=1

  def change_b(b)
      @b=b
  end

  def initialize(a,b)
    @id=@@id
    @@id+=1
    @a=a
    @b=b
    @map="#{a}_#{b}"
  end
end

class SubMap < Map
  def initialize(mapId)
      @mapID=mapId
  end

  def change_b(b)
    #
    #
    ObjectSpace.each_object(Map).select {|m| m.instance_variable_get(:@id) == @mapID}.instance_variable_set(:@b, b)
  end
end

map=Map.new(5,2) #Create New Map            ID: 1, @b = 2
second_map=Map.new(6,1) #Create Test Map    ID: 2, @b = 1
point=Map::SubMap.new(1) #Just affect the map with the ID of 1
point.change_b(5) #Change map's (Instance of Map with ID of 1) instance variable of @b to 5
p map
#<Map:0x00000002276f80 @id=1, @a=5, @b=5, @map="5_2">

另一种方法,使用您的地图实例作为工厂来创建子地图(此处没有复杂的代码):

class Map
    @@id=1

    def change_b(b)
        @b=b
    end

    def initialize(a,b)
        @id=@@id
        @@id+=1
        @a=a
        @b=b
        @map="#{a}_#{b}"
    end

    def sub_map
        return SubMap.new(self)
    end

    def to_s
        "#{@a}_#{@b}"
    end
end

class SubMap
    def initialize(map)
        @map=map
    end

    def change_b(b)
        @map.change_b(b)
    end
end

map=Map.new(5,2) #Create New Map            ID: 1, @b = 2
second_map=Map.new(6,1) #Create Test Map    ID: 2, @b = 1
puts second_map
# 6_1

puts map
# 5_2

point = second_map.sub_map
point.change_b(5)
puts second_map
# 6_5

两个答案都是正确的,但旧对象将被垃圾收集并丢失。我认为您应该将创建的对象保存在 class 数组中。

class Map
  @@maps = []

  def change_b(b)
    @b=b
  end

  def initialize(a,b)
    @id=@@maps.size + 1
    @a=a
    @b=b
    @map="#{a}_#{b}"
    @@maps << self
  end

  def self.find_by_id(id)
    @@maps.select {|m| m.instance_variable_get(:@id) == id}.first
  end
end

class SubMap < Map
    def initialize(mapId)
      @map = Map.find_by_id(mapId)
    end

    def change_b(b)
      @map.change_b(b)
    end
end

Map.new(5,2) # => #<Map:0x007ff94a822800 @id=1, @a=5, @b=2, @map="5_2">
Map.new(6,1) # => #<Map:0x007ff94a821fb8 @id=2, @a=6, @b=1, @map="6_1">
point=SubMap.new(1) # => #<SubMap:0x007ff94a820938 @map=#<Map:0x007ff94a822800 @id=1, @a=5, @b=2, @map="5_2">>
point.change_b(5) # => 5

也可以直接实例化SubMap,不需要Map::SubMap.new(1).

我建议按如下方式进行。

class Map
  @id=1
  singleton_class.send(:attr_accessor, :id)
  @id_to_instance = {}
  singleton_class.send(:attr_accessor, :id_to_instance)
  attr_reader :b

  def initialize(a,b)
    self.class.id_to_instance[self.class.id] = self
    self.class.id += 1
    @a=a
    @b=b
  end

  def change_b(b)
    @b=b
  end
end

class SubMap < Map
  attr_reader :mapID
  def initialize(mapID)
    @mapID=mapID
  end
  def change_b(b)
    self.class.superclass.id_to_instance[@mapID].change_b b
  end
end

map        = Map.new(5,2)
second_map = Map.new(6,1)
point      = SubMap.new(1)
point.change_b(5)

确认正确实例变量的值 @b 已更改

Map.id_to_instance[point.mapID].b
  #=> 5
  • 我使用了 class 实例变量 @id (在 Map 中)而不是 class 变量 @@id 因为前者被屏蔽了subclasses,这是很好的做法。
  • 我创建了一个 class 实例变量 @id_to_instance(在 Map 中),它将计数器 @id 的值映射到 [=14= 的关联实例].
  • 我为 class 实例变量 @id(在 Map 中)创建了 getter 和 setter,因此 Map 可以读取和递增实例变量的值。
  • 我为 class 实例变量 @id_to_instance(在 Map 中)创建了 getter 和 setter,因此 Map 可以设置它的值,SubMap 的实例可以读取它的值。
  • 我已经为实例变量 @b(对于 Map 的实例)创建了一个 getter 供 SubMap.change_b 使用。
  • Map#initialize 将键值对添加到哈希 @id_to_instance 中,将 class 实例变量 @id 的当前值映射到 [=14] 的实例=] 正在创建,并递增计数器 @id.
  • SubMap#change获取Map的实例变量id_to_instance的值等于SubMap实例的实例变量@mapID的值并在 Map.
  • 的实例上使用参数 b 调用实例方法 :change_b