Writing a "switchboard" in Java is both enlightening and useful. Comparing it to a C++ version is also educational.
Introduction
In the June 1998 issue of CUJ, I presented an article, "Inter-Object Messaging Using a SwitchBoard," which demonstrated how to pass messages from one object (the sender) to another (the receiver) without the sender being compiler-dependent on the receiver. A typical example is that of an ApplicationShell that needs to send a saveYourself message to persistent object PersonnelList. Now, imagine ten other persistent objects, just like PersonnelList, that are also interested in the saveYourself message. Great benefits can be gained by reducing the compiler dependencies between the objects, as well as by the simplification of message registration and delivery. The C++ implementation in my previous article relied heavily on template classes to bind objects to the SwitchBoard, template functions to properly construct the template classes, and function overloading to determine which templates to instantiate.
When looking at Java, you might at first think that a SwitchBoard is unnecessary. After all, Java is interpreted, classes are loaded at run time, and Java has only one implementation file (no headers/source maintenance required). But since Java compiles each class into architectural neutral byte codes, these dependencies, albeit more manageable, still exist. If an object of class A contains a handle to an object of class B and class B's interface changes, then class A must be recompiled, or it will throw a run-time exception.
In this article, I present a SwitchBoard implementation in Java. I also show how the Java implementation differs from the C++ implementation as a result of Java's lack of templates, of using a different memory-management paradigm, and of using some Java features not found in C++.
The Java SwitchBoard
The SwitchBoard object allows one object to send a message to another object, or objects, without the caller (sender) knowing the identification or location of the receiver. An object interested in receiving a message simply subscribes to the SwitchBoard, thus becoming a subscriber. Subscribers receive notification by way of messages passed through the SwitchBoard. Continuing the earlier example, the PersonnelList would subscribe to the SwitchBoard for the saveYourself message, and the ApplicationShell would post saveYourself when the persistent objects need to be saved. At that time, the SwitchBoard would dispatch the message to each subscriber of saveYourself.
The SwitchBoard class diagram for the Java implementation consists of two main classes, SwitchBoard and SubscriberHookup; an interface, SwitchBoardListener; and an adapter class, SwitchBoardAdapter (see Figure 1). The corresponding C++ class diagram is slightly different. The C++ implementation uses a template instead of an interface (see Figure 2). In both cases, the SwitchBoard class takes subscriptions, manages the list of subscribers, and provides a method for sending messages to the subscribers. The SwitchBoard follows a Singleton pattern, which insures that only one instance of itself exists per application. SwitchBoard enforces this by making the constructor private (or protected) and by providing only a static utility interface. Every method requiring access to the SwitchBoard singleton must retrieve a handle to it by calling the static instance method of the SwitchBoard class. instance checks to see if a SwitchBoard has already been instantiated, and if not, instantiates it. (The use of a utility interface also provides an easier access method than having to keep track of a handle to the SwitchBoard.) The biggest difference between the C++ implementation and the Java implementation is that Java does not support global variables; everything must be contained in a class. (This facilitates more object-oriented solutions.)
How the SwitchBoard Works
The code required to implement a rudimentary SwitchBoard in Java can be seen in Listing 1, SwitchBoard.java.
Objects interested in subscribing to the SwitchBoard call the SwitchBoard's subscribeTo method with a SwitchBoardListener argument; objects interested in canceling a subscription call unsubscribeFrom. In the C++ implementation, the hookup class, SubscriberHookup, automatically does the subscribing in its constructor and unsubscribing in its destructor. The Java SubscriberHookup class (Listing 2, SubscriberHookup.java) doesn't do it this way, because Java is a garbage collected language.
Garbage collection has many advantages. It frees the Java programmer from having to deallocate memory; it reduces (but does not eliminate) the number of memory leaks; and it simplifies the code by reducing the need for most destructors. However, garbage collection does have disadvantages. For example, you cannot control when objects actually get destructed that is, when the destructor (finalize in Java) is called. In C++ applying the delete operator to an allocated SubscriberHookup results in its destructor being invoked. In our example, the hookup would unsubscribe to the SwitchBoard. However, Java cannot guarantee when garbage collection will occur unless it is explicitly invoked, which of course defeats its purpose. Therefore, the Java programmer must write code that explicitly unsubscribes from the SwitchBoard.
Also related to garbage collection, if an object subscribes to the SwitchBoard, it must unsubscribe to be destructed. As long as the SwitchBoard, or any object for that matter, holds a handle to the subscriber, the garbage collector considers that subscriber still in use. The garbage collector finalizes only those objects that are no longer referenced.
SwitchBoard's only other public methods are a set of overloaded notify functions. These functions send messages to subscribing objects. These notify methods are all declared static. Before they can perform their specified tasks, they must retrieve a handle to the SwitchBoard, via instance.
The SwitchBoard keeps track of the SubscriberHookups in a Hashtable member. A Hashtable, a Java utility collection class, associates a key with a value. In this case, the value is a Java Vector object, which in turn contains all the SubscriberHookups. Similar classes exist in the C++ STL (map, vector, etc.). A big difference between C++ and Java collection classes is type safety. Java collection classes store all objects as references to a base type Object. The developer must cast this Object into the appropriate type. C++, on the other hand, uses templates to accommodate the type of object being stored. No casting is required.
The SwitchBoard and SubscriberHookup classes work together to maintain who is whom and who is interested in what. The SubscriberHookup class, however, cannot contain a handle to the subscribing object's receiver method because the receiver's type is unknown at this time. (If the hookup did contain such a handle, it would force the subscribers to all be the same type.) Instead, the SubscriberHookup class holds a reference to a SwitchBoardListener interface, a class that implements the SwitchBoardListener. Interfaces in Java, like abstract classes in C++, can be used to define methods, or behavior, that a class must implement. Java also uses interfaces as a clever alternative to multiple inheritance, but that is another article.
The only information stored by a hookup is the name of the subscription, which is accessed when the SwitchBoard's notify method is dispatching a message via method getSubscription. SwitchBoard calls SubscriberHookup's getSubscriber method to get a handle to the subscribing SwitchBoardListener object. Since the SubscriberHookup class does not know about the subscriber class it must rely on the SwitchBoardListener interface.
Using the SwitchBoard
Using the Java SwitchBoard differs from using the C++ SwitchBoard. The C++ SwitchBoard takes advantage of template functions and template classes to automate the subscription, or binding of the callbacks. In Java, a SwitchBoardListener implementation (a class that implements the interface) must be passed as an argument to the unsubscribe/subscribe methods. Table 1 shows a side-by-side comparison of C++ versus Java implementations to deliver a message from one object to another.
Possibly the strangest looking thing in Table 1 is the use of the SwitchBoardAdapter class in the row marked "Subscribing to the SwitchBoard." The SwitchBoardAdapter implements the SwitchBoardListener interface. This adapter serves two purposes: the developer does not have to implement all the interface methods, just the ones of interest; and the callback can be implemented at the point where it is installed, by anonymous subclassing.
An anonymous class is a shorthand way of creating a helper class, an inner class (or "nested" class in C++) without an explicit name. The adapter class contains empty methods for each possible callback, just as the C++ template does; but unlike the case with C++ templates, the Java callback must be written by hand. Anonymous classes are one of the major advantages of Java. They are used extensively for callbacks when writing GUI code with the AWT (Abstract Window Toolkit). Java's anonymous classes are much more useful than their C++ counterparts, because Java's classes have access to the enclosing class even when they are declared in the body of a method. This is evident in the example where the anonymous SwitchBoardAdapter deliver method calls the containing class's busy method without a handle to the containing class. (For more on the difference between Java and C++ anonymous classes, see the sidebar.)
Even with the flexibility of anonymous classes and interfaces, C++ provides a much safer and cleaner way of passing data through the SwitchBoard. In Java, specific data types are passed through the SwitchBoard cast to an Object, and the receiver must downcast the Object into the appropriate type (as in the deliver method). The Java instanceof operator insures the safety of the downcast. It is similar to using typeid or dynamic_cast<> in C++. C++, on the other hand, can use template methods to provide a type-safe object delivery mechanism or a new template instance for each type.
A Busyness Example
Many applications that do complex GUI processing can take advantage of a centralized-application busyness management center using the SwitchBoard. The busyness management center notifies all the GUI objects (application shell and dialogs) when to use the working or normal cursor, as well as updates and resets the status bar. By reference counting a "busyness" variable, each routine can independently control its busyness and not worry about prematurely ending the busy state of the application. Furthermore, objects can query the application to determine busyness and make decisions accordingly.
The example shown in Listing 3, Example.java expands on the PersonnelList example by posting the busy/notBusy messages in the PersonnelList.store method, and by implementing an ApplicationShell class to demonstrate how to handle and coordinate these new messages.
Note that the ApplicationShell class instantiates both the ApplicationShell and the PersonnelList objects in its main method, but neither of the instantiated classes knows anything about the other. After the objects have been constructed, main calls the ApplicationShell save method. The save method simply posts the busy message, and then the saveYourself message, which calls the PersonnelList.store method. The PersonnelList.store method also posts the busy/notBusy messages, thus incrementing and decrementing the busyness reference count accordingly. Finally, the save method calls ApplicationShell.refresh, which posts its own busy/notBusy messages, and posts the last notBusy message. Several of the busy messages are posted with a status string to display in the status bar (in this example, stdout). The last notBusy message results in restoring the status bar to what it was before all the work started. Running the program displays the status information to standard out:
Ready. Saving the application state... PersonnelList - saving... Saved. Refreshing... Ready.Conclusion
Java is fast becoming a widely used programming language, for some very good reasons. It is simple, portable, robust, object-oriented, GUI/Web enabled, distributed, threaded, and most importantly, it is just plain cool. Another reason for its growing popularity is the ease with which C++ programmers can make the transition to Java (without too many headaches, that is). It is definitely not just a toy!
As pointed out in this article, the same things can be accomplished in either Java or C++, but the solution differs based on which language you choose. For instance, a C++ solution will probably take advantage of several features that Java doesn't support, such as template classes, template functions, type safety, default parameters, etc. Likewise, the Java solution will most likely involve the use of interfaces and anonymous classes. At first, it might seem that Java needs to incorporate all these missing C++ features, but I'm not so sure incorporating all of them would be for the best. Today, Java is simple, yet powerful. The more features incorporated, the more complicated the language will become, and the more difficult it will be to use and understand.
If you are interested in learning more about Java from a C/C++ perspective, or if you are just looking for a good book on the subject, try Java 1.2 and JavaScript for C and C++ Programmers by Michael C. Daconta, et. al. While this article may only scratch the surface, this book covers it all. [Also check out Chuck Allison's new column, "import java.*", right here in this issue. mb] o
William L. Crowe is currently employed as a consultant for Sprint. He is a Software Architect who specializes in object-oriented design, GUI design, and large-scale distributed computing. He can be reached via email at billcrowe@earthlink.net.