4

这是我用于模拟模型的类“层次结构”的一部分(我的代码在 Python 中,但我认为我的问题与语言无关):

class World:
# highest-level class, which "knows" everything about the model
# most likely will have just one instance
# contains (e.g., in a dictionary) references to all the instances of class Agent

class Agent:
# each instance represents an agent
# an agent can, among other things, move around according to certain rules
# movement depends on the internal state of the agent,
# but also on the terrain and other information not stored in the Agent instance

问题:我应该把move实例方法放在哪里?

我认为我应该将依赖限制class Agent在层次结构中低于自身的类(即,其实例包含在Agent实例中的类)。但这意味着move方法不能存在,class Agent因为它创建了对描述地形等的类(至少是接口)的依赖 - 所以我不妨添加对Agent(因此依赖于)的引用World。从软件设计的角度来看,这可以吗?

另一种方法是将方法move放入class World,它不会导致任何额外的依赖关系。但是,class World然后将完成几乎所有的工作,在我看来,这将违背 OOP 的主要思想(我的理解是不要将所有功能集中到一个地方,而是将其包含在相关类中) .

性能方面的考虑只是次要问题(而且我认为这两种方法之间的性能不会有所不同)。

编辑:我误用了上面的“类层次结构”一词。我指的不是继承层次结构,而是一堆实例相互包含的类。

4

5 回答 5

3

您需要考虑的是单一职责原则。基本上,每个类都应该负责一个“事物”,并且应该完全封装那个责任。你应该只继承责任延伸的地方。您应该始终能够说扩展类是父类的 100% 甚至更多(在特定意义上更多)。您永远不应该遇到孩子是父母的子集并且“更少”的情况。因此,扩展世界的人不是一个好的设计,因为世界的某些方面与人无关。

因此,如果我们看一个示例,您会将实例方法放在由该特定类的角色决定的级别上。所以,让我们更明确地看一个例子:

class Person:
    name: ""
    birthDate: ""

class PoliceOfficer extends Person:
    badgeNumber: ""

显然这是伪代码,但它演示了正在发生的事情。

现在,你会在哪里添加一个move()方法?我们可以将它添加到PoliceOfficer,但是我们会破坏 的封装,Person因为一个人也可以移动。

class Person:
    def move(world):

但是,我们应该在哪里添加一个issueTicket()方法?generalizedPerson不能开票,所以如果我们将它添加到Person类中,我们将打破它的责任。因此,我们将其添加到 中PoliceOfficer,因为这是有意义的地方。

就创建依赖而言,您应该始终支持组合而不是继承。所以从这个意义上说,可以有尽可能多的依赖关系,因为它们都是软依赖关系(嗯,有点)。由于move()需要一个实例world(或具有世界接口的对象),因此依赖项被推出类并进入调用代码。这样一来,您的班级代码就可以保持相当开放和无依赖,同时仍然保持高效。

硬编码依赖项通常被视为不好的做法。但是注入它们(通过依赖注入或组合)通常被视为一件好事。

总结:将实例方法放在合理的地方。

于 2011-02-01T20:47:42.687 回答
1

我会move上课Agent。如果不需要,他Agent应该不会知道整个世界,而只会知道他需要移动的相关信息。不过,如果他真的了解整个世界,那也不算太糟糕。以下是一些原因:

  1. move如果您将方法放入类中,您将如何移动单个代理World?是否要将要移动的实例传递Agent给该方法?这似乎很丑陋。
  2. 如果您希望单个代理做某事,从 OOP 角度来看,最好在实例方法中执行此操作。
  3. 您也可以从另一个不知道世界,但知道具体代理的类的实例调用 move 方法。

但是,如果您的所有代理同时移动并且您不希望单个代理移动,您可以只将一种方法moveAllAgents放入世界级,然后遍历代理列表并移动所有这些代理。然后,您不需要类move中的方法Agent

于 2011-02-01T09:23:58.247 回答
1

将 move 方法放在有意义的地方,World 不能移动,Agent 可以。

如果您希望能够访问世界级功能,请为您的构造函数方法提供一个参数并传入世界实例。

world = World()
agent = Agent(world)

这使您的代理可以显式访问世界,而不是假设某种层次结构。

您可以更进一步,要求世界中的所有游戏对象都将世界作为参数。您可以通过创建您的代理和其他游戏对象继承的基本 GameObject 类来强制执行此操作。

class GameObject:
    def __init__(self, world):
        self.world = world

class Agent(GameObject):
    def __init__(self, world, startX, startY):
        # don't forget to call the super and pass the world to it
        super(Agent, self).__init__(world)
        self.startX = startX
        self.startY = startY

    def move(self):
        print 'I can see the world'
        print self.world

编辑:为了进一步扩展我的解释,如果你有一个Enemy类并且敌人也有一个move()方法,那么你可能希望敌人向代理移动。但是,您不希望敌人向世界询问代理的位置,相反,您可以将代理的引用存储在敌人内部,因为它是“目标”,并在需要时随时检查它的位置。

class Enemy(GameObject):
    def __init__(self, world, target):
        super(Agent, self).__init__(world)
        self.target = target

    def move(self):
        if self.target.x > self.x:
            self.x += 5
于 2011-02-01T09:20:49.350 回答
0

但这意味着 move 方法不能在类 Agent 中,因为它创建了对描述地形等的类(至少是接口)的依赖

代理人必须了解他的周围环境。这意味着代理必须使用描述地形的接口,是的。不过,我不会称其为“依赖”。假设实现 ITerrain 的类实际上遵循 ITerrain 接口并没有错。:-)

所以你把 .move() 放在代理上。然后 move() 方法将检查周围环境并尝试根据移动规则穿过它们。

于 2011-02-01T09:43:06.023 回答
0

不太了解您的应用程序,您还可以允许代理“了解”您世界的某个部分(可能定义为可以执行移动的最大区域,受您实施的有关代理可以走多远的一些规则的限制移动任何一个对 .Move 的调用)。因此,代理可能包含对更大世界的“剪辑区域”的引用(这个概念是从 .net GDI+ 图形对象中使用的“剪辑矩形”中窃取的)。

在更一般的意义上,我同意其他人的观点:在代理类上定义 Move 方法是完全有意义的,并且代理类拥有对其周围环境的感知是可以接受的。

虽然 OOP 倾向于最小化不必要的依赖关系,但当它有意义时,它就有意义了。现实世界中的人知道他们的周围环境,并且能够发起在这些环境中从一个位置移动到另一个位置所需的动作。

于 2011-02-01T14:46:58.130 回答