devxlogo

Using the Observer Pattern to Update Dependent Objects

Using the Observer Pattern to Update Dependent Objects

any applications need to update their components based on information input coming from a single source. It’s often important for these applications to maintain consistency among these components at any given point in time.

Suppose your application needs to maintain consistency between the states of its components or objects with a remote server state that is regularly sending various types of data to your app. Further, your application needs to display the received data in different formats to users through several kinds of components. All of this must be in sync with the data coming in. Since the server sends several different kinds of data, each component is interested in only some of the data. These components may be GUI or non-GUI based.

In this situation, many of your objects’ states are dependent on one central object. As this central object changes its state, that change has to propagate to the other dependent objects.

Using pointers or references to update these dependent objects requires you to include header files containing the class definitions of the dependent objects in your central object file. This way, you need to change the central object every time you need to include a new dependent object or exclude an old dependent object. This method is not very efficient and, in some cases, may be impossible.

The Observer Design Pattern
You can solve this complicated problem rather efficiently using the Observer design pattern. This pattern defines a one-to-many dependency between objects so that when the central object changes state, all its dependents objects are notified and updated automatically.

The Observer pattern defines the central object as the Subject and the dependent objects as Observers. Observers subscribe themselves to the Subject, and whenever the Subject’s state changes, it notifies the Observers using a consistent interface.

The sample code in this article provides two abstract classes: Subject and Observer. Both provide several pure virtual functions to implement the required interface.

The Subject Class
Inheriting your classes from Subject and Observer eliminates the need to include the header files of your dependent objects in your central object’s file. Simply include the Observer class’ header in the Subject class and vice versa. This achieves decoupling, or the abstract coupling, between theses classes because it renders them aware only of the interfaces?not the implementations.

The Subject need not know the real Observers to keep a list of references to them. It can just keep a vector of the Observer* type and a references to all Observers.

To subscribe with the Subject, your Observers use the method Subject::AddObserver() of the Subject. To unsubscribe, use Subject::DeleteObserver(). The Subject is not at all concerned with the different types of dependent objects subscribing and unsubscribing to it. It has a list of Observers and it notifies them by using Observer::Update(). The Subject is further not concerned with how the Update () is implemented by the Observers. You can pass a reference to the Subject to your Obsever by using Observer::AddSubject().

This class included in this article’s code provides the required implementation of all of the pure virtual functions of the Subject class. It stores the subscribed Observers in a thread-safe vector. Its functions are thread-safe. Making it thread-safe helps allows observers to get subscribed, unsubscribed, and notified safely.

Updating the Observers
The Observer::Update() is the single interface between the Subject and Observer and how the Subject notifies the Observers. One way to perform these notifications and updates us to use the pushsample code provides mainly the implementation of this method).

Using push, Observers do not need to be aware of the Subject interface for pulling data after getting notified. It requires only one method call.

One drawback of this method is that only the Observers, which are interested in common data or state change, can share this method. Another drawback is that you cannot send bulk data. You can solve this problem by keeping a pointer or reference to the Subject in the Observer. If the Observer is aware of the Subject, then it can call the functions of the Subject and pull the data.

If the Subject is receiving a different type of data than your Observers need, update the Observers by mixing the functionality of the template and virtual keywords. Normally, these keywords do not go together because template is a compile time activity and virtual is a runtime activity. The sample code mixes these by making the data going to the observers behave polymorphically.

To do this, the code uses two classes: MessageBase and MessageDetail:

class MessageBase{//--};templateclass MessageDetail : public MessageBase{\--};

When the Subject needs to notify the Observers, it packs the data, message, or changed state, in MessageDetail and passes it as a pointer to MessageBase to Observer::Update(). The Subject uses Subject::CreateMessage() to pack:

templateMessageBase* CreateMessage(int msgtype, type data){	msg = new MessageDetail(msgtype, this, data);	return msg;}

Using this technique, the Subject is able to push any kind of data to the Observers.

Suppose the Subject needs to push string and int to the Observers. The following code shows what happens:

Messagebase* msg = CreateMessage(0, "DEVX");Observer::Update(this, msg); msg = CreateMessage(0, 6);Observer::Update(this, msg);

When an Observer receives these messages, it uses RTTI to find its relevant data. Here’s the Observer that is interested in receiving the string data:

Update(Subject* sub, MessageBase* msg){	if (sub == m_Sub)	{		if (MessageDetail* m = dynamic_cast* >(msg)){// will come here only the data is of string type}	}}

This can be done for any kind of data.

What if many Observers need the same type of data, but depending on the different values, need to take action? You can use the argument msgtype (from CreateMessage()) to address this problem. msgtype forces Observers to recognize different values of the same data type and update themselves accordingly. msgtype is defined as int in MessageBase, but you can change it according to your needs.

Using ObserverQueue
It’s possible for the number of Observers to increase so much that the Subject gets overloaded, slowing down the time it takes to receive data from the server.

In this case, you put a middleman between the Subject and the Observers: ObserverQueue. The technique used to reduce the burden on the Subject goes like this uses two steps:

  1. Subscribe the Observers to ObserverQueue.
  2. Subscribe the ObserverQueue to the Subject.

Basically, ObserverQueue acts as a proxy Subject for the Observers, and ObserverQueue becomes the single Observer to actual Subject. ObserverQueue waits in its own thread and when the Subject updates, it comes out of its waiting state and notifies the real observers. Having only one Observer to update frees the Subject up to continue receiving messages from the network.

I’ve implemented ObserverQueue as a singleton, which allows one ObserverQueue per application. This technique allows you to have many-to-many relationships between Subjects and Observers, as shown in Figure 1.

Figure 1. ObserverQueue: Implementing ObserverQueue as a singleton allows you to have many-to-many relationships between Subjects and Observers.

You can subscribe the ObserverQueue as the Observer to as many Subjects as you want. You can also subscribe as many Observers to the ObserverQueue as you want. You even can update your Observer from more than one Subject. The ObserverQueue notifies all Observers with the data coming from all Subjects. The Observers can keep an array of Subject pointers from which they need data and while receiving data, they can check the Subject* that comes with MessageBase* with the pointers they have. If they find them equal, they can retrieve the data.

Words to the Wise
Making use of ObserverQueue is optional. Should you decide to use it then, you’ll need to take care of and remember a few things:

  1. Get the ObserverQueue pointer by using the ObserverQueue::CreateObserverQueue().
  2. After subscribing your Observers to the ObserverQueue, keep a reference to the ObserverQueue in your Observers. This is important because when Observers die, they need to call ObserverQueue::ReleaseQueue(). The ObserverQueue keeps count of its Observers?when the count gets to zero it deletes itself.
  3. Keep the references to the Subjects in the Observers. Just in case.
  4. You can use Observer::AddSubject() to either store the reference to the ObserverQueue or the Subject. However, you still need to define one more function in your class to store references to both of these.
  5. The MessageBase* inclluded in update() contains the pointer to the Subject from which the data originated. The Subject* included in update() points to the ObserverQueue. So, in order to search for the data from the Subject, you need to check the Subject* of MessageBase*. You can use GetRealSubject() of it to get the Subject.
  6. In every Observer, whether it uses the data to update itself or not, you must call Done() on the MessageBase*. This tells the Message class when to destroy itself. In fact, MessageBase also keeps track of all the Observers it has to go through and when Observers call Done(), it reduces the count. When the count reaches zero it delete itself.
  7. To update your GUI component, you need to override Observer:: ShouldNotifyByMessage() and Observer::GetHandle(). Return true from the former and the handle of your window from the latter.
  8. Because this framework uses boost threads for multi-platform support, you need to have boost libs. The VC++ 6 project has settings to include boost files.

Extending This Application
As you can see, the sample code updates all Observers with data coming from all Subjects. This is not very efficient, because the Observer knows in advance from which Subject it wants to receive the data. Introducing a Change Manager to this framework keeps a map between the Subjects and the Observers interested in receiving their data. This allows the ObserverQueue to determine which Observers are interested in data from which Subject and update accordingly.

Another thing you may like to do is to embed ObserverQueue inside the Subject. This makes the users of this framework opaque to its internal representation.

The sample code doesn’t inform Observers about Subject deletion, which may lead to dangling Subject pointers in the Observers. Adding a function to notify Observers about Subject deletion addresses this problem.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist