0

When I write python code for simulations, I often end up with the following situation: I have a class describing the general environment which contains a list of instances of a class that describes the individuals being simulated. In turn, they contain a class describing their behavior which depends on the environment.

So to illustrate it with dummy code;

class Environment:
    def __init__(self, individuals, parameters):
        self.individuals = individuals
        self.parameters = parameters

    def step(self):
        for individual in self.individuals():
            individual.do_something(self)

class Individual:
    def __init__(self, behavior, parameters):
        self.behavior = behavior
        self.parameters = parameters

    def do_something(self, environment):
        self.behavior.behave(self, self, environment)

Behavior is an abstract class and there may be multiple implementations of it, requiring informations on the state of the individual and/or the environment to work.

In the context of modelling for research, we usually don't have an absolute answer on which information the Behavior need, so we want to be able to write multiple Behavior subclasses to test different hypotheses. This implies that the Behavior instance should ideally be able to access the state of its container (and in turn, to the state of the container of its container etc; the whole container chain) when receiving the behave message.

I see two possible solutions to make this possible:

  1. Accumulating references to each containers as the messages goes down through the containers chain. This is what is done in the example code above. The cons of this method that I see is that if the containers chain gets longer, the message-passing functions (such that Individual.do_something in the example) gets progressively bloated with a large number of arguments.

  2. Giving to each instance a reference to its container, so it can access all the upstream containers chain. The cons I see is that either the rule of demeter may be broken doing so, either a strong coupling may be introduced between the classes.

So both solutions have drawbacks. I would like to know how this specific case is usually adressed, in python if possible.

1 Answer 1

1

"This implies that the Behavior instance should ideally be able to access the state of its container (and in turn, to the state of the container of its container etc; the whole container chain) when receiving the behave message."

No, not quite - that would very likely lead to tight coupling between a concrete behavior and the container classes all the way up the chain, which would make it difficult to swap one behavior implementation for another (potentially a significantly different one), without making changes to the other classes as well.

Any time you want to decouple a higher level component, and make it independent of some lower level component, even though it's calling it, you apply dependency inversion in some form. The way it works is that the higher level component defines an abstraction of some kind, and then the lower level component is made to adhere to that abstraction. The abstraction in question is mostly assumed to be an interface that the lower level component must implement, but more broadly speaking, it can also be something like a data model that the low level component must understand, or any kind of convention, etc. Since Python is dynamically-typed, this will be easier to do then in other languages, as the interface can be implicit, but on the other hand, more discipline may be required on the part of the programmer(s) regarding what methods one object is allowed to call on another.

enter image description here Dependency inversion - note that "Package A" owns/defines the abstraction (the interface); ownership is important, as that's what inverts the dependency, even if there are no separate deployment units.

So, there are a few ways to go about this. The way you described it, it seems that either (a) your behavior classes have very little to no data and get most of what they need from other classes (which is not ideal), or (b) they do have behavior-specific data, but there is some shared relevant data owned by the containers that they need as input (which is a bit better, design-wise). For example, I assume that the Environment class is relevant to almost all behaviors.

In either case, you can design the methods in the Behavior class to take in the data they need as parameters, but be very clear about what methods they are allowed to call on those parameters - so that, for example, if you pass in the Environment, or an Individual instance, they don't call the things that you might want to change or remove later on (because that's how you get tight coupling, and that's how you get your containers stuck with methods that are only needed by some specific behavior implementations, but not others). Perhaps a better alternative is to have this data stored in a separate object, which defines its own interface, and pass that object in (because that way the behaviors can't call unwanted stuff on their containers, so it requires less programmer discipline).

Now, I said earlier that (a) is not ideal. Ideally, in OO, you want to make the Behavior subclasses as self-contained as possible. They shouldn't have to poke around other objects to extract data (that's a code smell that indicates that maybe most of that data should be moved to the Behavior class, or its subclasses); instead, they should own the related data, and their interface should mostly be designed so that the calling code can just "ask" them to do something for it (maybe passing in a few contextual parameters), and then forget about it, carrying on with its own business (basically, that's what Law of Demeter is really about). So if you can figure out how to model your classes in this way, you'll get the substitutability of the behavior classes that you need.

Another option that may be feasible (but may be an overkill - you'll have to think carefully about it and decide based on your knowledge of the problem area) is a design based on the Observer pattern as described in the Go4 book.

enter image description here

The Observer pattern itself is not too important here (as it solves a different problem), but some elements of it's structure could be. Essentially, Individual would correspond to the Subject (which, notice, is not abstract), but it would have only one abstract Observer (the Behavior). But what happens in the Layer B is the interesting part. If a concrete behavior (concrete observer) needs some state that needs to be placed in the Individual class (the subject), but that state is not general enough so that it applies to all behaviors, you can derive a special-purpose Individual, and have the concrete behavior maintain a reference to it (e.g., you pass it in during construction), so that it can get that state when it receives the update() message. Notice that Layer A is completely unaware of this.

As a final note, if you do go for this kind of thing, don't make this structure mandatory - if, in a particular case, there's no need for a derived Individual class, then don't create one.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.