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:
Team
class;AssistantReferee
class that determines whether the ball is out of bounds;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:
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:
handle_sideline_situation
function is doing more things that it should be doing, and will soon become a huge unmaintainable mess;AssistantReferee
object is doing things that are not properly the job of an assistant referee.What is really happening in the game we're trying to model is:
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.