Using Design Patterns to Make Better Models: Observer
After exploring the Strategy pattern in a previous post, we're continuing our look at how design patterns can help us structure simulation models.
In the first post, we looked at how Strategy can separate behaviour that varies from the object that uses it.
This time, we're looking at a different problem: how objects communicate when something changes.
The Observer pattern provides a useful way of structuring that communication.
A quick recap: design patterns
Design patterns provide established approaches to recurring software-design problems.
They are commonly grouped into three categories:
Creational patterns – concerned with creating objects.
Structural patterns – concerned with how classes and objects are organized.
Behavioral patterns – concerned with how objects interact and how their behaviour is structured.
The Observer pattern is a behavioral pattern.
At its core, Observer establishes a one-to-many relationship between objects.
One object, known as the subject or observable, maintains a collection of observers. When something changes, the subject notifies its observers.
A useful definition is:
The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.
In simpler terms, instead of an interested object repeatedly asking:
"Has anything changed yet?"
the object experiencing the change can simply say:
"Something changed."
and notify everyone who is interested.
Let's see what that looks like in an armed-response simulation.
The situation
Suppose we're building a simulation of an armed-response system operating in a neighborhood.
Our model contains:
a population of houses;
a dispatcher responsible for managing break-in incidents;
a security vehicle that responds to incidents; and
a mechanism for measuring response time.
For this example, we'll use six houses and a security agent.

The basic process is straightforward:
A house is initially safe.
A break-in occurs.
The house enters a BreakIn state.
The dispatcher identifies the incident.
The security vehicle travels to the house.
The response is completed.
The model records the response time.
We also have a data object and a histogram that let us examine the response times the model produces.

So far, nothing particularly complicated.
But there is an important question:
How does the dispatcher know that a break-in has occurred?
Structuring the model
Before looking at the two implementations, we need to understand the model's structure.
We create an AbstractHouse containing the behavior common to our house agents.
The house has a visual representation, a breakInIncident object, and a state chart containing two states:
Safe
BreakIn

The BreakIn state represents the event that the rest of the system needs to react to.
We then create an AbstractDispatcher containing the common dispatcher behavior.
This gives us a structure that allows us to create two implementations of the same system:
BadHouse and BadDispatcher
GoodHouse and GoodDispatcher
BadHouse doesn't change anything from AbstractHouse. It simply inherits the existing behavior.
The difference is in the dispatcher.
Our BadDispatcher adds a timeBetweenChecks parameter and a recurring event called ev_checkHouses, which we will discuss in the next section.


Both implementations share the Security agent because its response behavior doesn't need to change.
It contains the state chart that controls the vehicle's response:
Idle
Traveling
Responding

Finally, Main contains the separate Bad and Good populations and their spatial configuration. We specify the X and Y coordinates of the houses and security office for each implementation independently.
For now, we'll focus on the Bad implementation.
The first implementation: polling for break-ins
The Bad Dispatcher uses a recurring event to check the houses.
Every timeBetweenChecks, the dispatcher loops through the houses and looks for break-in incidents not yet assigned to security.
Conceptually, it is doing this:
for (AbstractHouse h : main.badHouses) {
if (h.inState(h.breakIn)
&& h.breakInIncident.getSecurity() == null) {
col_pendingBreakInIncidents.add(h.breakInIncident);
}
}If it finds an incident, it adds it to the collection of pending incidents and passes it through the dispatcher to the security vehicle.
The important thing is how the dispatcher discovers the incident.
The house already knows that it has experienced a break-in.
But the house doesn't tell the dispatcher.
Instead, the dispatcher repeatedly asks the houses whether anything has happened.
This is polling.
Initially, this seems perfectly reasonable.
If the dispatcher checks often enough, it will eventually discover every break-in.
But there is a problem.
The complication
Imagine the dispatcher checks all six houses at 12:00.
Everything is safe.
One minute later, a break-in occurs.
The house immediately enters its BreakIn state.
But the dispatcher doesn't know.
If timeBetweenChecks is one hour, the dispatcher might not discover the break-in until almost 13:00.
The response process therefore looks something like this:

This means our response time is affected by two things:
Detection delay + travel/response time
The detection delay is not caused by the security vehicle traveling too slowly.
It is caused by the way we've designed the communication between the houses and the dispatcher.
But we could just check more frequently...
One obvious solution is to reduce timeBetweenChecks.
Instead of checking every hour, we could check every 10 minutes.
Or every minute.
Or even every second.
The shorter the interval, the less time a break-in has to wait before the dispatcher discovers it. But there is a trade-off.
The dispatcher now has to execute its checking event much more frequently.
We therefore have two competing effects:

This gives us a useful experiment to run in AnyLogic.
Experimenting with the Bad Dispatcher
We've added a radio-button control that allows us to change the time between checks.
The options are:
2 hours
1 hour
10 minutes
1 minute
1 second

This lets us examine two different effects.
First, what happens to the response time?
Second, what happens to the execution speed of the simulation?
2-hour checks
With the dispatcher checking every two hours, a break-in can go undetected for a long time.
The response-time distribution therefore includes the delay before the next check.

As we reduce the checking interval, the detection delay becomes smaller.
For example:
2 hours → 1 hour → 10 minutes → 1 minute → 1 second
The dispatcher gets more opportunities to discover newly occurring break-ins.




But the experiment has another side.
If we run the model at Virtual Time, we can observe how quickly the simulation itself progresses.
With a two-hour checking interval, ev_checkHouses executes relatively infrequently.
With a one-second interval, it executes dramatically more often.

As we reduce the time between checks, the model has to process more checking events.
So while we can improve response time by checking more frequently, we also increase the work the simulation has to do. We move from simulating 50 years a second to just 30 days a second!
We improved the model's detection behavior, but we did so by making the dispatcher repeatedly inspect the houses, and the model became significantly slower!
This is where the Observer pattern gives us another approach.
Introducing the Observer pattern
Instead of asking:
"Has anything happened?"
the dispatcher could wait for the house to say:
"Something happened."
This is the key idea behind our Observer implementation.
The house becomes the subject.
The dispatcher becomes an observer.
When a break-in occurs, the house notifies its observers.
We create an Observer interface that defines the notification behavior.


For our model, the important capability is that an observer can be notified of a break-in.
The dispatcher implements this interface.
This gives us an important separation:
The house doesn't need to know how the dispatcher responds.
It only needs to know that it has an observer capable of receiving the notification.
Building the Good implementation
Our GoodHouse still inherits from AbstractHouse.
But unlike BadHouse, it adds the functionality required for the Observer pattern.
The house maintains a collection of subscribers:
ArrayList<Observer> subscribers;These are the observers that should be notified when something happens.
We then add a function responsible for sending the notification.

The notification function is called when the house enters the BreakIn state.
Instead of waiting for the dispatcher to discover the break-in during its next scheduled check, the house immediately notifies its observers.

The Good Dispatcher implements the Observer interface.
Its notification function receives the break-in incident and can decide what to do with it.
For example, if the security vehicle is available, it can assign the incident and begin the response process.

Notice what has disappeared.
There is no timeBetweenChecks.
There is no recurring ev_checkHouses event.
There is no loop repeatedly asking every house whether it has experienced a break-in.
The dispatcher no longer needs to search for changes.
The subject tells it when a change occurs.
Connecting the observers
We still need to tell the houses who their observers are.
On Main, we initialise the model by adding the appropriate dispatcher to the houses' subscriber collections.

The communication flow is now fundamentally different.

That is the Observer pattern in action.
The result
We can now run the two implementations and compare their response times.
The Bad Dispatcher introduces a variable detection delay because it discovers incidents only during scheduled checks.
The Good Dispatcher receives a notification when the break-in occurs, removing the need for periodic polling.

The result is a lower response-time distribution because we removed the artificial waiting period introduced by polling.
More importantly, we have also removed the repeated checking event, thus our simulation runs faster!
The Good Dispatcher doesn't need to spend simulation time repeatedly asking every house whether something has changed.
The house already knows, it simply communicates the change when it happends.
Why does the interface matter?
This design offers another benefit.
The Observer pattern doesn't just change when the dispatcher receives information.
It changes the dependency between our objects.
The house doesn't need to depend directly on a particular dispatcher implementation.
It depends on the Observer interface.
That means we can change who is observing the house without changing the house's break-in behavior.
For example, we could add another observer:

Or we could change the dispatcher implementation while keeping the same notification mechanism.
This makes the model easier to extend.
For example, we can make a small modification to demonstrate this: change which observers are subscribed to the houses and show that the subjects do not need to know what those observers do. For example, let's add a police station as an observer. The police station only needs to implement the BreakInObserver interface and nothing more.




The house simply sends its notification.
The observers decide how to respond.
In the last image, you'll notice two vehicles at the break-in house: both the security and police observers we just created have responded. You can configure this so that only one observer responds, or multiple observers respond to the same notification.
This is one of the key benefits of programming to an interface rather than directly to a concrete implementation.
What actually changed?
It's worth stepping back and comparing the two models.
The underlying armed-response process hasn't fundamentally changed.
We still have houses.
We still have break-ins.
We still have a security vehicle/s.
We still measure response time.
What changed is how information about the break-in moves through the model.
That distinction is important.
Like the Strategy example, the Observer pattern isn't about changing what the simulation represents.
It is about improving how the model is structured.
When should we use Observer?
The Observer pattern can be useful when one object needs to communicate changes to one or more other objects, particularly when the number or identity of those observers may change over time.
In our example, the houses are naturally the subjects because they know when their state changes.
The dispatcher is an observer because it is interested in those changes.
This also gives us a useful rule of thumb:
If an object repeatedly checks another object to see whether something has changed, consider whether the changed object could notify it instead.
That doesn't mean polling is always wrong.
Sometimes periodic checking is exactly what a model requires.
But when the event itself can trigger the communication, the Observer pattern can provide a cleaner alternative.
Conclusion
The original model worked.
The Bad Dispatcher could discover break-ins, assign them to security, and produce response times. But it relied on repeatedly polling the houses. That introduced a trade-off.
Checking less frequently increased detection delay.
Checking more frequently reduced detection delay but increased the amount of work the simulation had to perform.
The Observer pattern gave us another approach.
Instead of having the dispatcher repeatedly ask the houses whether anything had changed, the houses notify their observers when a break-in occurs.
The House is responsible for knowing when its state changes.
The Observer interface defines how interested objects can be notified.
The Dispatcher is responsible for reacting to those notifications.
And Main is responsible for connecting the subjects and observers.
More importantly, the relationship is no longer tied to one specific dispatcher.
We can add, remove, or change observers without having to rewrite the subject's behaviour.
That's the real lesson of the Observer pattern:
When one object needs to know when another object changes, consider letting the changed object notify its observers rather than repeatedly polling it.
In our simulation, that gives us a model that is not only more responsive, but also easier to extend as the system grows.
And that's exactly what we're looking for when applying design patterns to simulation models: not just a model that works, but a model that is easier to change when the requirements inevitably do.
The example model can be downloaded below:
To learn more about using Java and other Object-Oriented Design Principles and apply these in AnyLogic simulation models, see our online course Java for AnyLogic.
Devon Cowling is a simulation engineer and data scientist working with The AnyLogic Modeler. To contact him, use LinkedIn.
With contributions from Selaelo Kgoale
What next?
If you liked this post, you could read more posts by following the links above. Why not subscribe to our blog or follow us on any of the social media accounts for future updates? The links are in the Menu bar at the top, or the footer at the bottom. You can also join the mobile app here!
If you really want to make a difference in supporting us please consider joining our Patreon community here.
If you want to contact us for some advice, maybe a potential partnership or project or just to say "Hi!", feel free to get in touch here, and we will get back to you soon!



Comments