Replace Recorded History With Event Notification

This is a pattern in the RefactoringLanguage.

One object is controlling the operation of another, as might be required when using the StrategyPattern, StatePattern or MediatorPattern. The controller makes decisions on changes to the state of the controllee. It detects changes of state by recording the last state of the controllee, and comparing it to the new state.

Keeping track of the history of several state variables makes the controller class become large and complicates its logic.

Therefore: Change the controllee to notify the controller when it changes state.

The controllee knows when it is changing state, because it is implementing those state changes. It can announce the event to the controller, which therefore no longer needs to record the a history of the controllee's state in order to detect state changes.


Example

A simulation of a race car and race manager. The Car class simulates driving around the track and can be told ,among other things, to drive into the pits. Once in the pits, the car can be refueled and given new tyres.

Note: the code examples elide exactly how the track is represented and how the car determines when it has reached the pits because it is not relevant to this pattern.

class Car {
[...]

void comeIntoPits() { [...] } boolean isInPits() { [...] } void refuel() { [...] } void changeTyres() { [...] }

void animate( double t ) { moveCarAlongTrack(t); if( shouldComeIntoPits() && atPits() ) { stopAtPits(); } } }

The Race Manager decides when to bring the car into the pits, according to its own race strategy, and must refuel the car when it has stopped in the pits. It detects when the car comes into the pits by recording whether the car was in the pits on the last "tick" and comparing that to whether the car is now in the pits.

  class RaceManager? {
private Car car;
private boolean carWasInPits = false;

void think() { if( carNeedsToComeIntoPits() ) { car.comeIntoPits(); } else if( !carWasInPits && car.isInPits() ) { car.refuel(); car.changeTyres(); }

carWasInPits = car.isInPits(); // Record history } }

This gets unwieldy as the RaceManager? tracks more state variables. Therefore, remove the state variables from the RaceManager? and change the car to notify its manager of any changes in state.

1) Change the Car so that it announces events when it changes state.

 | interface CarListener? {
 |carInPits( CarEvent? ev );
 |[...]
 | }

class Car { void comeIntoPits() { [...] } boolean isInPits() { [...] } void refuel() { [...] } void changeTyres() { [...] }

|void addCarListener( CarListener? listener ); |void removeCarListener( CarListener? listener );

|private void fireCarInPits() { [...] }

void animate( double t ) { moveCarAlongTrack(t); if( shouldComeIntoPits() && atPits() ) { stopAtPits(); |fireCarInPits(); } } }

2) Change the RaceManager? so that it receives events from the Car.

class RaceManager? implements CarListener? {
private Car car;
private boolean carWasInPits = false;

public void think() { if( carNeedsToComeIntoPits() ) { car.comeIntoPits(); } else if( !carWasInPits && car.isInPits() ) { car.refuel(); car.changeTyres(); }

carWasInPits = car.isInPits(); // Record history }

|public void carInPits( CarEvent? ev ) { |} }

3) Move the logic that handles the car stopping at the pits into the event handler"

class RaceManager? implements CarListener? {
private Car car;
private boolean carWasInPits = false;

public void think() { if( carNeedsToComeIntoPits() ) { car.comeIntoPits(); } else if( !carWasInPits && car.isInPits() ) { | }

carWasInPits = car.isInPits(); // Record history }

public void carInPits( CarEvent? ev ) { | car.refuel(); | car.changeTyres(); } }

4) Remove the variable recording whether the car was in the pits at the last "tick".

class RaceManager? implements CarListener? {
private Car car;

public void think() { if( carNeedsToComeIntoPits() ) { car.comeIntoPits(); } }

public void carInPits( CarEvent? ev ) { car.refuel(); car.changeTyres(); } }

--NatPryce


I'm a little uncomfortable with the code example because it misses something like a Pits class which would be responsible to determine whether or not the the Car is in the Pits and to take care of calling the refueling and the changeTyres methods.

As far as the refactoring is concerned I have the feeling that it is part of a more general refactoring. Something like Replace Centralized Control by Event Notification. (Sven_Gorts@hotmail.com)

The code elides details of the track that the car is moving along. In practice, the car has a position on the track, and that position knows whether it is a position on the pit lane or on the main race track. However, that is not really relevant to this refactoring. I've added a note to that effect above.

As for the "Replace Centralized Control by Event Notification", the Race Manager is still acting as a centralized controller of the car. The refactoring has just changed the way that the Race Manager detects the events that require a control calls to be made to the car. --NatPryce


class RaceManager? implements CarListener? {
private Car car;

public void carInPits( CarEvent? ev ) { car.refuel(); car.changeTyres(); }

|public void carFuelLow( CarEvent? ev ) { |car.comeIntoPits(); |}

|public void carFlatTire( CarEvent? ev ) { |car.comeIntoPits(); |} } -- DavidAndersen?


EditText of this page (last edited August 2, 2002) or FindPage with title or text search