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.