Where should instance methods be written in the class hierarchy?

Here is the part of the hierarchy class that I use for the simulation model (my code is in Python, but I think my question is language independent):

class World: # highest-level class, which "knows" everything about the model # most likely will have just one instance # contains (eg, 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 should limit the dependence of class Agent classes lower in the hierarchy than myself (that is, classes whose instances are contained in instances of Agent ). But this means that the move method cannot be in the class Agent , because it creates a dependency on (at least the interface) classes describing the landscape, etc. - therefore, I could also add to Agent link to (and therefore dependency on) World . Is this okay in terms of software design?

An alternative is the move method in class World , where it will not cause any additional dependencies. However, class World will do almost all the work, and it seems to me that this will run counter to the main idea of ​​OOP (I understand that not to collect all functions in one place, but rather to contain it in the corresponding classes).

Performance considerations apply only to a minor issue (and I don't think that performance will be different between the two approaches).

EDIT: I misused the words "class hierarchy" above. I did not mean the inheritance hierarchy, just a bunch of classes whose instances contain each other.

+4
source share
5 answers

What you need to take into account is the principle of one responsibility . In principle, each class should be responsible for one “thing” and should fully encapsulate this responsibility. And you should only inherit when the liability is extended. You should always be able to say that an expanding class is 100% of the parent and more (more in a certain sense). You should never have a situation where the child is a subset of the parent and “less”. Thus, a person expanding the world is not a good design because there are aspects of the world that are not related to the person.

So, if we look at an example, you should put the instance methods at the level that is dictated by the role of this particular class. So, consider a more approximate example:

 class Person: name: "" birthDate: "" class PoliceOfficer extends Person: badgeNumber: "" 

Obviously, this is pseudo-code, but it demonstrates what is happening.

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

 class Person: def move(world): 

But where will we add the issueTicket() method? A Person cannot issue a ticket, so if we add it to the Person class, we will violate it. So instead, we will add it to PoliceOfficer , as that makes sense.

As for creating dependency, you should always maintain composition over inheritance . So in this sense there can be as many dependencies as you want, since they are all soft dependencies (well, sort of). Since move() takes an instance of world (or an object with a world interface), the dependency is pushed out of the class and into the calling code. This way, your code will remain open and free from dependencies, while maintaining performance.

This is usually considered bad practice for hard code dependencies. But injecting them (via injection or dependency composition) is usually seen as a good thing.

In short: Put instance methods where their logical meaning is.

+3
source

Place the moving method where it makes sense, the world cannot move, the agent can.

If you want to access world-class functions, give your constructor a parameter method and pass an instance of the world.

 world = World() agent = Agent(world) 

This gives explicit access to the World from your agent, and does not imply any hierarchy.

You can do this even further and require all game objects in the world to perceive the world as a parameter. You can apply this by creating a base GameObject class that inherits your Agent and other game objects.

 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 expand my explanation further, if you have an Enemy class, and the enemy has a move() method, most likely you might want the enemy moving in the direction of the agent. However, you do not want the adversary to ask the world about the agent’s position, instead you can just keep a link to the agent inside the enemy, as he is “targeted” and check his position at any time.

 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 
+1
source

I would put move in the Agent class. If not needed, Agent does not need to know the whole world, but only the relevant information that he needs to move. However, if he knows the whole world, this is also not so bad. Here are a few reasons:

  • How would you move one agent if you put the move method in the World class? Do you want to pass an instance of Agent to move to this method? That seems pretty ugly.
  • If you want one agent to do something, it is better to do this in the instance method from the point of view of OOP.
  • You can also call the move method from an instance of another class that the world does not know, but a specific agent.

However, if all your agents move at the same time, and you do not want individual agents to move, you could just put one moveAllAgents method in a world class and then moveAllAgents over the list of agents and transfer all of these agents. You then do not need the move method in the Agent class.

+1
source

But this means that the move method cannot be in the agent of the class, because it creates a dependency on (at least the interface) classes that describe the landscape, etc.

The agent must know about his surroundings. This means that the Agent must use the interfaces that describe the landscape, yes. I would not call it addiction. There is nothing wrong with the fact that the class that implements ITerrain actually follows the ITerrain interface. :-)

So you put .move () on the agent. Then the move () method will check the environment and try to move around them in accordance with the rules of movement.

0
source

Unaware of your application, you can also let the Agent “know” about a certain part of your world (possibly defined as the maximum area in which movement can be performed, constrained by some rules that you implement about how far the Agent can move in any .Move call). Thus, the agent may contain a link to the “clip area” of the larger world (this concept is stolen from the “Clip rectangle” used in .net GDI + Graphics Object).

In a more general sense, I agree with others: it makes sense to define the Move method in the agent class and that it is acceptable for the agent class to know its environment.

While OOP seeks to minimize unnecessary dependencies, when it makes sense, it makes sense. A person in the real world is aware of his environment and is able to initiate the action necessary to move within this environment from one place to another.

0
source

Source: https://habr.com/ru/post/1337919/


All Articles