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.
所以它的工作原理如下:
ObjectSpace.each_object(Map)
它获取 Map
的所有实例的枚举器,您可以在此处找到更多信息:How do I list all objects created from a class in Ruby?
Enumerator.select {|m| m.instance_variable_get(:@id) == @mapID}
从我们在第 1 步中获得的 Enumerator,我们 select 只有实例变量 @id 等于当前 subMap 的 @mapID 的实例。
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
我有一个系统,用户可以在其中创建 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.
所以它的工作原理如下:
ObjectSpace.each_object(Map)
它获取 Map
的所有实例的枚举器,您可以在此处找到更多信息:How do I list all objects created from a class in Ruby?
Enumerator.select {|m| m.instance_variable_get(:@id) == @mapID}
从我们在第 1 步中获得的 Enumerator,我们 select 只有实例变量 @id 等于当前 subMap 的 @mapID 的实例。
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