4

Here's a part of a class "hierarchy" that I use for a simulation model (my code is in Python, but I think my question isn't language dependent):

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

Question: where should I put the move instance method?

I thought I'm supposed to limit the dependency of class Agent to classes lower in the hierarchy than itself (i.e., classes whose instances are contained in Agent instances). But that means move method cannot be in class Agent since it creates a dependency on (at least the interface of) classes that describe terrain, etc - so I might as well add to Agent a reference to (and hence a dependency on) World. Is this ok from software design perspective?

The alternative is to put method move in class World, where it won't cause any additional dependencies. However, class World would then be doing almost all the work, and it seems to me that it would go against the main idea of OOP (which I understand as not to pile all the functionality into one place, but rather contain it in the relevant classes).

Performance considerations are only of minor concern (and I don't think performance would differ between the two approaches anyway).

EDIT: I misused the words "class hierarchy" above. I wasn't referring to inheritance hierarchy, just to a bunch of classes whose instances contain each other.

max
  • 49,282
  • 56
  • 208
  • 355
  • I don't think I'd do it either way to be fair. Try writing some code that 'uses' your domain before you write it, then fill in the blanks. Starting from the outside and working inwards often helps. A quite a few books on domain design and modelling are available. – Iain Ballard Feb 01 '11 at 09:13
  • Do you have any references you think might help? I only found one book on domain design on Amazon: http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215/ref=sr_1_1?ie=UTF8&qid=1296686494&sr=8-1. – max Feb 02 '11 at 23:23

5 Answers5

3

The thing that you need to take into account is the Single Responsibility Principle. Basically, each class should be responsible for one "thing", and should completely encapsulate that one responsibility. And you should only inherit where the responsibility is extended. You should always be able to say that the extending class is 100% of the parent and more (more in a specific sense). You should never have a situation where the child is a subset of the parent and is "less". So a person extending a world is not a good design since there are aspects of the world that do not relate to a person.

So, if we look at an example, you would put the instance methods on the level that is dictated by the role of that particular class. So, let's take a more defined look at an example:

class Person:
    name: ""
    birthDate: ""

class PoliceOfficer extends Person:
    badgeNumber: ""

Obviously this is pseudocode, but it demonstrates what's happening.

Now, where would you add a move() method? We could add it to PoliceOfficer, but then we would break the encapsulation of Person since a person can also move.

class Person:
    def move(world):

But, where would we add an issueTicket() method? The generalized Person cannot issue a ticket, so if we added that to the Person class, we'd be breaking the responsibility of it. So instead, we'd add it to PoliceOfficer, since that's where it makes sense.

As far as creating dependency, you should always favor composition over inheritance. So in that sense, there can be as many dependencies as you'd like since they are all soft-dependencies (well, kind of). Since move() takes an instance of world (or an object with the world interface), the dependency is pushed out of the class and into the calling code. So that lets your class's code remain pretty open and dependency-free while still being productive.

It's generally seen as bad practice to hard-code dependencies. But injecting them (through Dependency Injection or Composition) is typically seen as a good thing.

In summary: Put instance methods where it makes logical sense to put them.

Community
  • 1
  • 1
ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • Thank you - Just to clarify: I wasn't planning to inherit `Agent` from `World`. `World` object simply contains (references to) all the `Agent` objects created in that world. – max Feb 01 '11 at 20:55
  • @Max: Ok, well then they are in different class hierarchies (which wasn't clear to me from your question). So use injection or composition to add the references of Agent into the World... And don't hard-code World into the Agent class. Pass the references back and forth (so you could add `World` to the constructor of `Agent`, which then calls the `word.addAgent(self)` method)... That way you're still keeping the dependency outside of the class while allowing for what you're trying to do... I hope that helps... – ircmaxell Feb 01 '11 at 20:59
  • The dependency I'm concerned about is this. `agent.move()` method will need to know how to check the terrain. And of course, the interface to check the terrain is part of a different class, which I hoped to keep completely independent from class `Agent`. Is this dependency ok? – max Feb 02 '11 at 00:38
  • @max: well, it is and it isn't. don't be dependent on the actual class instance (that's tight coupling). Instead, create an interface that describes the required functionality, and depend upon that. That way you're loose coupled to the classes, but tightly coupled to the interface (which is ok, since any class can implement the interface). – ircmaxell Feb 02 '11 at 00:45
  • An additional complication: `world` object wants to quickly check if there's an agent at a given location. If the `move` method in the `Agent` class, it would have to update the internal table inside `world` that maps points to `Agent` objects. What to do??? – max Feb 02 '11 at 21:17
  • I decided to ask a separate question based on my last comment: http://stackoverflow.com/questions/4880691/which-class-should-store-the-lookup-table – max Feb 02 '11 at 22:41
1

Put the move method where it makes sense, the World can't move, the Agent can.

If you're looking to be able to access world-level functionality, give your constructor method a parameter and pass the world instance in.

world = World()
agent = Agent(world)

This give explicit access to the World from your agent, rather than assuming some sort of hierarchy.

You can take this a step further and require that all game objects within the world take the world as a parameter. You could enforce this by creating a base GameObject class that your Agent, and other game objects, inherit.

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

EDIT: To extend my explanation further, if you had an Enemy class and the enemy had a move() method as well, chances are good you may want the enemy to move towards the agent. However, you don't want the enemy asking the world for the position of the agent, instead you can just store a reference to the agent inside the enemy as it's "target" and check it's position any time you need.

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
Soviut
  • 88,194
  • 49
  • 192
  • 260
  • +1: the idea to create the GameObject class to help manage world objects. – max Feb 02 '11 at 00:39
  • But I'm wondering about the enforcement aspect. Does it really ensure that subclasses will take, and correctly process, `world` as an argument? I mean, if I accidentally create `class X(GameObject): def __init__(self): pass`, I won't really get alerted to that problem would I? – max Feb 02 '11 at 00:49
  • No, not necessarily, but such is the case with any extension of a class. It's up to the extender to correctly implement the extended class. Were you using a more strongly typed language you could try to enforce it more using interfaces, but even that can be circumvented. Just let go of the notion of strict enforcement and document your class correctly so you know how to extend it properly. – Soviut Feb 02 '11 at 02:52
1

I would put move into the class Agent. If not needed, the Agent shouldn't know the whole world, but only the relevant information he needs to move. If he does know the whole world, however, that is not too bad either. Here are some reasons:

  1. How would you move a single agent if you would put the move method in the World class? Do you want to pass the instance of Agent to be moved to that method? That seems quite ugly.
  2. If you want a single agent to do something, it is nicer to do that in an instance method from an OOP perspective.
  3. You can also call the move method from an instance of another class that does not know the world, but the specific agent.

However, if all your agents move simultaneously and you don't want single agents to move, you could just put one method moveAllAgents into the world class and then iterate through the list of agents and move all of these agents. You then don't need a move method in the Agent class.

Sören
  • 2,661
  • 2
  • 19
  • 22
  • Actually, in my case it does look like I only move agents all at once. But I'm afraid it may change in the future, so I'm afraid to commit to that. In any case, is the fact that I always move agents together a good reason to do without `move` method in `Agent`? – max Feb 02 '11 at 00:33
  • well, i think it is kind of a good reason, because then u only have to call one method and u don't need to iterate over all agents and call the move method on each one. However, if you may change it in the future, I would better implement the `move` method in `Agent` - otherwise, u will have a lot of refactoring work to do. An alternative would be to combine the two: have a method in `World` where u iterate over all agents and call the `move` method on each agent. – Sören Feb 02 '11 at 08:44
0

But that means move method cannot be in class Agent since it creates a dependency on (at least the interface of) classes that describe terrain, etc

The agent must know about his surroundings. That means that the Agent must use the interfaces that describe the terrain, yes. I wouldn't call that a "dependency", though. There is nothing wrong with assuming that the class that implements ITerrain actually follows the ITerrain interface. :-)

So you put .move() on the agent. The move() method then would check the surroundings and try to move through them according to the rules of movement.

Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251
0

Not knowing much about your application, you could also allow an Agent to "know" about a certain portion of your world (possibly defined as the maximum area within which movement can be executed, constrained by some rules you implement about how far the Agent can move in any one call to .Move). So the agent might contain a reference to a "Clip Region" of the greater world (this concept stolen from the "Clip Rectangle" used in .net GDI+ Graphics Object).

In a more general sense, I agree with the others: it makes complete sense for the Move method to be defined on the Agent Class, and that it is acceptable for the agent Class to posses an awareness of it's surroundings.

While OOP tends towards minimizing unecessary dependencies, when it makes sense, it makes sense. A person in the real world is aware of their surroundings, and is able to initiat the action required to move within those surroundings from one location to another.

XIVSolutions
  • 4,442
  • 19
  • 24
  • And I found another typo while checking out your edit. It was EARLY, and I hadn't had my full does of coffee yet . . . – XIVSolutions Feb 02 '11 at 04:54
  • Don't worry, technically they're "computer generated images" so calling them "CGI" wasn't a false claim ;) – Soviut Feb 02 '11 at 05:39