哪个类应存储查找表?
这个世界里有很多代理(agent),每个地方只有一个代理。每个代理都知道自己在哪儿,但我还需要快速检查某个地方是否有代理。因此,我还维护了一个从地点到代理的地图。我现在面临一个问题:这个地图应该放在哪里?是放在 class World
里,还是放在 class Agent
里(作为类属性),或者放在其他地方。
在下面的例子中,我把查找表 agent_locations
放在了 class World
里。但是现在每当代理移动时,都得调用 world.update_agent_location
。这非常麻烦;如果我后来决定要追踪代理的其他信息,而不仅仅是他们的位置,那我是不是还得在 Agent
的代码里到处加上对世界对象的调用呢?
class World:
def __init__(self, n_agents):
# ...
self.agents = []
self.agent_locations = {}
for id in range(n_agents):
x, y = self.find_location()
agent = Agent(self,x,y)
self.agents.append(agent)
self.agent_locations[x,y] = agent
def update_agent_location(self, agent, x, y):
del self.agent_locations[agent.x, agent.y]
self.agent_locations[x, y] = agent
def update(self): # next step in the simulation
for agent in self.agents:
agent.update() # next step for this agent
# ...
class Agent:
def __init__(self, world, x, y):
self.world = world
self.x, self.y = x, y
def move(self, x1, y1):
self.world.update_agent_location(self, x1, y1)
self.x, self.y = x1, y1
def update():
# find a good location that is not occupied and move there
for x, y in self.valid_locations():
if not self.location_is_good(x, y):
continue
if self.world.agent_locations[x, y]: # location occupied
continue
self.move(x, y)
我也可以把 agent_locations
放在 class Agent
里作为类属性。但这样做只适合我只有一个 World
对象的情况。如果我后来决定要创建多个 World
对象,那查找表就得针对每个世界来设置了。
我相信一定有更好的解决方案……
补充:我在代码里加了一些行,展示了 agent_locations
是怎么用的。注意,它只是在 Agent
对象内部使用,但我不知道这种情况是否会一直保持下去。
3 个回答
抱歉,我不太明白这个问题。"""当代理决定移动到哪里时,他会检查确保自己不会碰到其他代理""". 很明显,一个地点最多只能有一个代理。肯定是Location类里有一个代理的属性。
locn = self.where_to_move()
if locn.agent is None:
self.move(locn)
elif locn.agent is self:
raise ConfusedAgentError()
else:
self.execute_plan_B()
好的,我想我可以分享一下我的看法,这可能更多的是一种意见,而不是明确的“这样做”建议(我没有接受过正式的编程培训)。
我认为每个 World
实例应该有自己的 agent_locations
。
我主要是从接口的角度来思考。在我看来,World 类应该负责管理你世界里的资源,这里指的是空间。既然 World
是空间的管理者,那么代理(Agents)应该向它询问空间是否可用(也就是没有被占用),而不是互相询问。因此,我觉得你的 self.location_is_good
调用更合适的写法是 self.world.is_location_available(x, y)
[1]
这样一来,世界就自然地负责检查给定空间的可用性。World 类可能还有其他变量来决定某个空间是否可用。比如,那里有没有灌木丛?或者其他东西。你可能已经在每个世界中有某种表格来记录你的 (x, y)
坐标。被“占用”可以是这些对象的一个属性。
另外,你的 World 已经知道每个代理的状态(通过 [(agent.x, agent.y) for agent in self.agents]
[2])。agent_locations
字典实际上是这些属性的索引或缓存,因此它应该属于 World
。
关于将状态发送回 World
的麻烦……嗯,你并不能通过让 Agent
来做这个来解决问题。但是调用 update_agent_location(self, agent, x, y)
是完全多余的,因为 x == agent.x; y == agent.y
(如果你反转调用它的行)。你可以在 World 中简单地有一个方法 update_agent_state(self, agent)
,这样 World 就可以用来更新它的索引。你甚至可以提交一个额外的参数来描述状态变化的类型(如果你不想每次都更新所有属性的话)。
class World(object):
# ...
def update_agent_state(self, agent, state_change=None):
# Update properties based on what changed, or
# drop state_change param and update everything everytime
if state_change == Agent.LOCATION_CHANGE:
self.agent_locations[agent.x, agent.y] = agent
elif state_change == Agent.WHATEVER:
pass
class Agent(object):
LOCATION_CHANGE = 1
def update(self):
for x, y in self.valid_locations():
if not self.can_move_to(x, y)
continue
self.move(x, y)
def can_move_to(self, x, y):
"""Determines if x, y is a location where we can move."""
if not self.world.is_location_available(x, y):
return False
if not self.has_money_to_travel_to(x, y):
return False
return True
def move(self, x, y):
"""Moves to x, y and notifies world of state change."""
self.x = x
self.y = y
self.world.update_agent_state(self, Agent.LOCATION_CHANGE)
大致就是这样(请查看我的脚注)。
[1] 除非当然,某个位置的“好坏”还取决于其他变量,比如如果你只有在 1) 位置可用并且 2) 代理有 1000 美元支付票价的情况下才能移动到 (x, y),那么你应该有一个 Agent.can_move_to(x, y)
方法,它会调用世界的方法,并检查它的钱包。
[2] 我假设你的 self.agents = {}
是个笔误,因为你不能在字典上 append
。你是指列表([]
)对吧?
在面向对象编程(OOP)中,谈论对象时可以用“是一个”和“有一个”来帮助理解。比如,一个World
(世界)“有一个”Agents
(代理)的列表和一个Locations
(位置)的列表。而一个Location
(位置)“有一个”Agent
(代理)。同时,一个Agent
“有一个”Location
和一个World
。
class Agent:
def __init__(self, world):
self.location = None
self.world = world
def move(self, new_location):
if self.location is not None:
self.location.agent = None
new_location.agent = self
self.location = new_location
def update(self):
for new_location in self.world.locations:
if self.location_is_good(new_location):
self.move(new_location)
def location_is_good(self, location):
if location.agent is not None:
return False
class Location:
def __init__(self, x, y):
self.x = x
self.y = y
self.agent = None
想象一下,如果给Location
(位置)添加一个新属性,比如地形(terrain),你会发现这种封装的好处非常明显。同样,如果要给Agent
(代理)添加新东西,比如武器,只需要写一个类似于move()
的武器专用功能就可以了。需要注意的是,World
(世界)根本不需要参与到move()
这个过程里。移动的操作完全是在Agent
(代理)和Location
(位置)之间进行的。