Subscribe now for rapid prototyping

The Publisher-Subscriber pattern reduces object dependencies for flexible UI design

Frequent requirement changes during a software development cycle can often lead to disruption and anxiety. Software team members scramble to update their API to accommodate new requirements. As a solution, the Publisher-Subscriber pattern can be instrumental in reducing object dependencies; thereby reducing the need for frequent API changes. In addition, the pattern proves useful when used for rapid prototyping. In this article, I show you how this versatile development tool can simplify your programming tasks again and again.

Benefits of a Publisher-Subscriber implementation

You can realize the following benefits by utilizing the Publisher-Subscriber pattern:

  • A publisher can publish events to 1-N subscribers. You can easily implement systems that contain more than one object that requires the same information.
  • Objects communicate with each other indirectly through domain-specific events so object dependencies are reduced or eliminated.
  • You can add new objects to a system with minimal impact because the API need not change. This characteristic accommodates systems with frequent requirement changes.
  • Objects have location transparency. Objects that receive events do not have to know the event’s origin. Conversely, an object can make a request via a publisher without concern for where the object carrying out the request exists.

What is the Publisher-Subscriber pattern?

The Publisher-Subscriber pattern is modeled after the publisher-subscriber relationship that exists between a consumer and a periodical publisher. In the Publisher-Subscriber pattern there are publisher and subscriber objects. Publishers are also referred to as senders, observables, subjects, broadcasters, or notifiers. The publisher object maintains a list of subscribers and notifies each subscriber when a state change occurs in an object of interest to that subscriber. A publisher has one to many relationships with subscribers. Although a system may contain one or more publishers, each publisher in all likelihood has many subscribers. For example, a system that publishes weather conditions can have several subscribers at any one time.

Subscribers are also referred to as receivers, listeners, observers, or callbacks. A subscriber registers to receive updates from a publisher by calling a publisher, providing a reference to itself for future callbacks and a parameter that indicates for what object the subscriber wants updates. After registering with a publisher, the subscriber receives unsolicited updates from a publisher whenever its monitored object experiences a state change. When you use a Publisher-Subscriber implementation to facilitate a communication framework, the subscriber is notified each time the event type for which it has registered is published.

The subscriber continues to receive updates until it terminates the subscription. This type of subscription is called a push subscription because data is sent to the subscriber without the client requesting each delivery. Each subscriber may want to monitor more than one object or receive more than one event type notification. For example, an online trading application may contain an object that subscribes to several different market data event types for a particular stock symbol. This online trading object may subscribe to events pertaining to the volume transacted for a specific stock symbol or to events relating to each trade for that symbol during any given day. At any time, a subscriber may unsubscribe to all updates without affecting other objects in the system.

In addition to its push subscription, Publisher-Subscriber also has a pull subscription. The pull implementation requires that the subscriber periodically request the publication from the publisher. You can use this implementation when it is not necessary to update the subscriber immediately.

Object identification

In several ways, a subscriber can indicate to a publisher what type of object it wishes to moderate. One implementation provides an object prototype (a prototype is an object instance) to the publisher to indicate what type of object the subscriber wants. The subscriber receives an update if the broadcasted state change is for an object that is an instance of the class the subscriber used when it registered for updates (as determined using the

instanceof

operator). This implementation lets the subscriber subscribe to every object in the class hierarchy simply by subscribing to an object of the base class. For example, an object that logs events in a system could subscribe to an event base class of the system and receive every event that occurs in that system.

Another implementation informs the publisher of the desired object type by passing in an identifier that identifies the object of interest. This identifier could be a String, an integer, and so forth. This implementation requires that each object provide an accessor method that returns an object type identifier, which allows a publisher to determine what type of object it is.

There are some advantages to using an identifier rather than a prototype to register a subscriber for updates. Using a prototype requires that a subscriber create an object for each subscription. Object creation is costly both in terms of memory and time. It also requires the publisher to use the instanceof operator when publishing to determine the object type. The instanceof operator is expensive in its use of execution time.

On the other hand, identifiers can be statically defined, use less resources (RAM), and do not have to be garbage collected when no longer needed. In addition, objects can be members of different hierarchies but be identified as the same type.

Rapid prototyping

The Publisher-Subscriber pattern is ideally suited for applications that need to distribute information to many objects. Beyond the more traditional uses, however, are some less obvious applications of this pattern. Publisher-Subscriber can also facilitate rapid prototyping. Rapid prototyping plays an important role in determining what technologies are necessary to create a software system.

When prototyping systems, you must often integrate new and/or divergent technologies into the system to determine if they work in the product. This task often requires experimenting with several combinations of technologies to determine the set that works best for your project. At the project’s prototyping stage, the software engineer may not have a design from which to work. Indeed the software design may depend upon the results of the prototyping efforts. Design is one of the most time-consuming tasks in developing a software system. Defining object roles and how objects will interact is a fundamental task that often requires a great deal of time. The Publisher-Subscriber pattern can simplify that task.

The Publisher-Subscriber pattern facilitates rapid prototyping by letting you create a communication framework based upon indirect communication. This event notification framework allows objects to subscribe to events published by other system objects. You can eliminate or postpone the step of defining how each object communicates with other system objects until a later date. You can use the Publisher-Subscriber pattern even if it is ultimately replaced by a different implementation. By using this pattern, you can postpone API commitments until your requirements are fully understood and stable.

Flexible UI design

One of the first objectives to address when building a new system is defining how the user will interact with the system. By using a Publisher-Subscriber implementation for event notification, you can create a flexible user interface (UI) design. Compare the examples shown in Figures 1 and 2.

Figure 1. UI for ticket reservation system
Figure 2. A more flexible UI for ticket reservation system

Figures 1 and 2 show two variations of a UI for a ticket reservation system. Each reservation UI consists of the following elements:

  1. A Control object that requests event information and ticket reservations
  2. 1 to N query results display(s)
  3. 1 to N status display(s)

Figure 1’s UI comprises one control panel (A), one query results panel (B), and one status panel (C). These components could coexist on the same panel and eliminate the need for a communication mechanism.

You can create a more flexible UI by using the Publisher-Subscriber implementation to provide a communication framework. You first assign each UI element a role, and then you create a set of events to serve as a communication mechanism between graphic elements. Once you establish a communication frame that eliminates dependencies between UI elements, you are free to design a more diverse set of UI possibilities.

Figure 2’s UI also has one control panel (A), but has two instances of the query results display (Bs) and status panels (Cs). Each display panel (shown on a JInternalFrame) can subscribe for the set of events generated by the control panel whenever the internal frame gains focus. Conversely, the internal frame that loses focus can unsubscribe for the events that the control panel generates.

The only implementation difference between these two UIs is the dynamic subscribing/unsubscribing in response to the focus event. You can create different objects that perform the same role in a system and compare the two performances. For example, a Java program that displays graphic images could define two objects that are both active in the system at the same time, each having the role of image renderer. Both objects in this role subscribe to an image render request event and receive a filename of the image to be rendered. One object could render the image using a Java solution. Another object could render the image by making Java Native Interface (JNI) calls to native code. You can compare the performance of the two image renderers and weigh the benefit of the Java solution’s portability versus the JNI solution’s increased performance.

You can also use the event notification system for solution comparison when determining which communication protocols to implement. For example, a system that needs to access a remote file could have two objects that have the role of remote data accessor. Each object subscribes to a file request event. One remote access object uses ftp to fetch a remote file, while another remote accessor object uses http to fetch a remote file. By comparing the transfer rate of these two protocols over a range of file sizes, you can determine which protocol is best suited for your system.

Implement Publisher-Subscriber in Java

Java provides the key elements — namely the Observable class and the Observer interface — to easily implement the Publisher-Subscriber pattern. The Observer interface is found in Java’s util package. By implementing the Observer‘s update() method, you can create a class to serve as your subscriber. The subscriber registers or subscribes with a publisher by passing the publisher a reference to itself and a parameter to indicate the object type it wants.

The parameter that specifies the object of interest can take many forms. A subscriber could provide a string that identifies a particular event type when subscribing. For example, a subscriber may register with a publisher to receive all status events by using an identifier STATUS_EVENT. The subscriber would then receive any event type that identified itself as a STATUS_EVENT.

Alternatively, the subscriber can register with the publisher using a prototype of the object for which it wants updates. Using this approach, a subscriber can be notified any time the object to be published is an instance of a status object. A publisher could determine dynamically whether the published object is the status class’s instance by using the instanceof operator:

    /** The StatusEventSubscriber subscribes to status events and
      * presents the status to the user via the setStatus call.
      */
    private final class StatusEventSubscriber implements Observer
    {
        /** The StatusEventSubscriber subscribes to all status
          * event types upon creation by registering itself with
          * the publisher.  The StatusEventSubscriber passes the
          * publisher itself (an Observer instance) and a parameter
          * that indicates that this subscriber is interested in
          * receiving status events (the STATUS_EVENT parameter).
          */
        public StatusEventSubscriber()
        {
            Publisher.getInstance().subscribe(this, STATUS_EVENT);
        }
          /** The subscribe() method is the implementation of the Observer interface.
            * This method is a callback method that the publisher
            * calls when the criteria for notification have been met.
            * In the case of an event notification system, this method
            * would be called when the event published is of the
            * same type as the event type that this observer has previously
            * subscribed to.
            */ 
        public void update(Observable observable, Object reservationEvent)
        {
            setStatus(((ReservationStatus)reservationEvent).getStatus());
        }
    }

Java’s Observable class, also found in Java’s util package, is a publisher. The Observable class maintains a list of Observers and notifies each Observer when changes occur in the Observable object. You can create a Publisher class to service a system by establishing a collection of Observable objects. Each Observable object in turn maintains a list of Observers. Your system’s specific needs will determine the Publisher‘s implementation.

You can create a Publisher by utilizing Java’s Observable and Hashmap classes. Each subscriber passes to the publisher a reference to itself for callbacks and a string to indicate for which event type it wants updates. A string can represent each event type used in your system. These event type strings are passed to a publisher and used as a key to an Observable instance. The Observable instance in turn maintains a list of all the subscribers interested in receiving the events of the key’s type.

An alternative implementation for the subscribe() method uses an object instance to specify the object of interest rather than an object identifier (such as a String). Subscribers subscribe to the event of interest by passing the subscribe() method an instance of the object that the subscriber wishes to monitor.

The following table illustrates a Publisher implementation. The Publisher in this instance associates 1-N Observers or Subscribers with a particular event type. The event types are identified by a String.

Publisher event types/Observable mapping

Event type Observable’s list of Observers
Status Event

Status Subscriber #1

Status Subscriber #2

Status Subscriber #3

Data Request Event Data Request Subscriber #1
Data Response Event

Data Response Subscriber #1

Data Response Subscriber #2

The following code shows a subscribe() method implementation within a Publisher object for an event notification system. Each object in the system subscribes to events by passing a reference to an Observer (the object that will process the event when it is published) and an identifier that indicates what type of event the object wants to receive.

    public void subscribe(Observer eventSubscriber, String eventTypeHashKey)
    {
        // Simple class that extends Observable
        EventObservable eventType = null;
        // If there already exists an Observable object that has a list
        // of subscribers subscribing to this particular event
        // type, and then add the new subscriber (eventSubscriber) to the list.
        if (mEventTypes.containsKey(eventTypeHashKey))
        {
            eventType = (EventObservable)mEventTypes.get(eventTypeHashKey);
            eventType.addObserver(eventSubscriber);
        }
        // This is the first subscriber to subscribe to an event of this
        // event type.  Create a new Observable instance and add the
        // new subscriber (eventSubscriber) to the list.
        else
        {
            eventType = new EventObservable();
            eventType.addObserver(eventSubscriber);
            mEventTypes.put(eventTypeHashKey, eventType);
        }
    }
    /**
     * This method is called by a subscriber when the subscriber no longer
     * wants to be notified about events of a specified type.
     */
    public void unsubscribe(Observer eventSubscriber, String eventTypeHashKey)
    {
        Observable eventType = null;
        if (mEventTypes.containsKey(eventTypeHashKey))
        {
            eventType = (EventObservable)mEventTypes.get(eventTypeHashKey);
            eventType.deleteObserver(eventSubscriber);
        }
    }
    /**
     * This publish() method is called to publish an event to 1 to N subscribers.
     */
    public void publish(Event reservationEvent)
    {
        String eventTypeHashKey = null;
        EventObservable eventType = null;
        eventTypeHashKey = reservationEvent.getEventType();
        eventType = (EventObservable)mEventTypes.get(eventTypeHashKey);
        eventType.notifySubscribers(reservationEvent);
    }

Subscribing to the pattern

The Publisher-Subscriber pattern provides a flexible framework for a software system. It accommodates frequent requirement changes by reducing object dependencies. The communication mechanism provides location transparency, making it well suited for use in distributed systems. A publisher object’s ability to publish information to multiple subscribers makes this pattern a good choice for rapid prototyping as well. Every programmer should become familiar with the Publisher-Subscriber pattern for development ease.

Source: www.infoworld.com