Why events?

Many applications, including applications in tutorials, use events and coordinators. Students (and professionals!) often find these confusing. Here I'll try to motivate their use.

Imagine you're programming a simulation of a soccer game. You've got the basic structure of the game coded up (a field, a ball, the score, and so on). You also have:

  1. a Team class;
  2. an AssistantReferee class that determines whether the ball is out of bounds;
  3. a ThrowIn class.

You don't need to worry about the specific choice of object here. For example, it doesn't much matter whether you have an AssistantReferee or the field itself determining whether a ball is out of bounds, or even whether the functions I'm going to describe are standalone functions or methods on objects you've defined. What's important to recognize is that you have some bits of code that are modeling causally interconnected events.

Now you need to write code to handle the situation when a ball goes out of bounds. A first pass might be something like:

class AssistantReferee:
    def is_ball_out(self, game_conditions):
        # logic goes here
        return some_boolean_value

    def handle_sideline_situation(self, game_conditions):
        if self.is_ball_out(game_conditions):
            team_to_award = game_conditions.team_that_did_not_touch_last()
            self.award(ThrowIn(team_to_award, location))
        else:
            self.maintain_good_posture()

You can ignore most of the details there--though note that we already need all sorts of custom objects and that, as usual, even a very coarse-grained representation of reality requires quite a lot of care to model.

What's important is that:

  1. This code does not use any explicit notion of an event or a coordinator;
  2. This code is bad and will be extremely brittle and will soon become an unmaintainable, failing mess;
  3. The ways in which it is bad arise precisely from our decision to model this situation without events and event coordination.

You're likeliest to notice things going wrong when you add some further bit of detail or functionality to your soccer game. For concreteness, let's say you want to model substitutions. There are times in a game when substitutions are allowed; throw-ins are one of them. We'll be tempted to simply add this to our previous code:

class AssistantReferee:
    def is_ball_out(self, game_conditions):
        # logic goes here
        return some_boolean_value

    def handle_sideline_situation(self, game_conditions):
        if self.is_ball_out(game_conditions):
            team_to_award = game_conditions.team_that_did_not_touch_last()
            check_for_pending_substitutions() # Are there players who are checked in and waiting?
            # Do whatever else needs to be done
            self.award(ThrowIn(team_to_award, location))
        else:
            self.maintain_good_posture()

Now there are two big problems--or, really, one big problem that can be described in several ways:

  1. Our handle_sideline_situation function is doing more things that it should be doing, and will soon become a huge unmaintainable mess;
  2. Our AssistantReferee object is doing things that are not properly the job of an assistant referee.
  3. The proper causal structure of the situation is being distorted in our code.

What is really happening in the game we're trying to model is:

  1. Something has happened;
  2. That thing causes other things;
  3. And, in particular, it could cause several other things that might themselves have consequences. (When a substitution happens, the scorer needs to know about it and a number of remaining substitutions might need to be updated; this will go on and on.)

To represent this faithfully, we need to treat the ball going out of bounds as its own thing. Maybe an assistant referee will detect it; maybe video review will; maybe the referee will. Maybe it needs to cause one thing to happen; maybe several. Maybe those several things need to happen in a certain order; maybe not. (And some events require no downstream effects.)

These causal relationships inevitably change, a lot, over the life of a piece of software. Keeping track of them is not the AssistantReferee's job. Note that in real-life soccer games, assistant referees do not, strictly speaking, award any throw-ins or make any rulings; they just send signals to referees. And referees are pretty good metaphors for coordinators: they receive events and know what those events need to be triggering. In your software as in real life, it would be a disaster to have all sorts of scorekeeping, game-management, and disciplinary judgements flowing through the assistant referee just because they happened to be the proximate agent to one part of one process.

Here I've just tried to convince you that events and event coordination are absolutely necessary. How exactly to implement them is tricky; I recommend Architecture Patterns with Python for a good overview of the subject. Good luck.


More posts about programming


Home page