Create a shared experience with the 'subject' design pattern How do you make software that allows a number of people to work together on the same group of things? Think of air traffic control, logistics planning, multiuser games, whiteboarding, or even software development itself. In each of these examples, there is both a group of people and a set of the things they’re working on, and what we really want to do is create a little universe in which they can all exist and interact with each other in realtime.This little universe, of course, isn’t new — it’s been accomplished in many ways in many different situations. But at some point we must ask ourselves if there is a more overarching way to solve this recurring problem.This article describes just such a solution — one I’ve built using Java’s elegant Remote Method Invocation (RMI). The Java code required to build the necessary infrastructure for this kind of software is remarkably minimal, and the result is extremely flexible. ‘Subjects’ definedWhile writing about this software problem or trying to explain it in the classroom or to my clients, I’ve found that I constantly use the phrase “objects-of-interest” in order to describe the thing — whatever it might be — that a given group of people was working on. Then it occurred to me that these things could simply be called subjects instead, though I knew that this might be a bit of a daring wordplay. (Think: server plus object equals: sobject, or subject.)If we want to build a sensible environment for a group of people to interact with a group of subjects, there are a number of requirements:Participants should be able to “tune in” to a selection of subjectsEveryone must receive quick notification when a subject’s attribute changesUpdate notification should be automatic, or listener-based, as it is in AWT and Swing componentsNetwork traffic should be minimized using a caching strategyRethinking client and serverRemote method invocation provides a way to build wormholes to join the object galaxies contained in different virtual machines. Effectively, RMI creates special reference “wires” that extend from one galaxy to another, and tries to makes sure that the method-call “sparks” along these wires visit the other galaxy and return intact. There are two important differences between local method calls and RMI: RMI sparks happen in slow motion compared to local method-call sparks, and the intergalactic wires can sometimes go down. Developers often choose to have RMI’s remote objects reside exclusively on a machine they call the server, with their counterpart proxies on the client machine. This is fine until you need to allow the server to initiate an event — for instance, in collaborative software in which one client’s updates must be broadcast to all the other clients. In this context, the client must also play the role of server in that it must be ready to receive updates at any time.Client/server and the collaborative systemIt’s time to rethink the ideas of client and server. The key difference between server and client objects in a collaborative system is their one-to-many relationship. The relationship can be nicely described as a fanning out of connections between the singular subject and the multiple remote subjects, which are the things that the people see.The issue of who is serving and who is clienting is moot, since all we really want is for everybody to see an up-to-date version of the subject. The collaborative whiteboard system built in the December ’97 issue of JavaWorld‘s Step-by-Step column uses a polling approach to ensure that everybody stays up to date. While this is easy to implement, it causes lots of unnecessary network traffic. A nicer solution is to have updates propagate only when they occur. A further look at collaborationThe essence of collaboration is that the subjects are shared among the participants. Everyone should be looking at the same thing, even if each wishes to see it in a different way. And when one person makes a change in a subject, the other people should see the results immediately — or as soon as possible.Collaborative software often demands quick access to the subjects for the purpose of rendering within a single view. This problem is not solved by the remote-observer-callback mechanism because the real data still resides on the server. What’s needed is some way to cache the current status of the shared objects so that a repaint causes no network traffic whatsoever. The solution presented here performs a simple and extreme form of caching: we maintain synchronized full replicas of the subjects on each client.In certain situations, updates will also occur as a result of some server events, not just due to client actions. Server-initiated updates must propagate to all clients. We want a piece of collaborative software to allow us to tune in to some chosen selection of the available subjects, whether our selection overlaps with that of other people or not. Typically, we won’t be actively modifying more than one subject at a time, but we would like to be shown all the changes the others are making as they happen. When you tune in, you also expect to encounter the subject in its current state.Entering the realm of subject-oriented programmingOnce the mechanism of maintaining synchronized replicas of subjects is in place, you’re liberated from the complexities of deciding which objects to make remote and which to make local, of how to deal with callbacks, and of how to keep screen updates working quickly. You just have to implement the Serializable interface and decide which things are to be called attributes. You have entered the realm of subject-oriented programming.To accomplish the creation and maintenance of synchronized replicas on the client and server, we use a pair of partner objects. Each consists of RMI remote objects and maintains proxies to the other. Although these two partners are separated from each other in entirely separate object galaxies (virtual machines), effectively they interpenetrate. The only fundamental difference between the partners is that the one residing on the server must concern itself with multiple client partners, while the one residing on the client only has one server partner. The paired objects are generic — they can be applied equally well in any situation requiring the subject pattern. For simplicity, I chose to make the objects vectors, but they could just as easily have been derived from a hashtable or any of the new Java 1.2 collection classes (see Resources).And now…the codeThe entire architecture hinges on one tiny interface called Subject, which defines the basic functionality of subjects both on the server and on the client. Since a subject is something with some given number of attributes, the Subject interface provides access to them in an indexed manner with its get() and set() methods.Subject The most important thing about this replica object is what the programmer sees of it, as represented by the following interface on both the client and the server: public interface Subject extends Watchable { Object get(int theIndex); void set(Object theObject, int theIndex) throws RemoteException; void dispose() throws RemoteException; } The dispose() method is needed to explicitly break up the linkage between client and server partner objects, since two remote objects referring to each other can never be garbage-collected on either side.Notice that the accessor method does not throw a remote exception! RMI programmers will be accustomed to defining interfaces in which each and every method throws a RemoteException, since all method calls represent communication. With the subject design pattern, however, the presence of a replica eliminates communication when accessing attributes. Because we always have a replica on hand, getting information from a subject is a local activity.Behind the mask of the Subject interface lies one of two different kinds of implementation objects, depending on whether the subject appears on the client or the server side. On the server side, the implementation is called a hub; on the client side, it’s called a tip, referring to the one-to-many or fan-out nature of this pattern (more about this later). Both implementations involve a remote object, zero or more proxies to other replicas, and a vector of contained attributes. Fortunately, through the use of the interface, the difference is completely invisible to the application software on either side. The Subject interface also derives from the Watchable interface, basically adding the functional equivalent of java.util.Observable. Any object, whether in the client or the server, can implement the simple Watcher interface and subscribe to a subject to receive update messages.The only way to create a new subject is through the use of the SubjectFactory class, which fabricates a fully connected client-side replica of its server-side counterpart. The bidirectional connection mechanism internal to the replicas is hidden from view by this factory process.public class SubjectFactory { ... public Subject createSubject(String theName) throws RemoteException ... } The SubjectFactory above makes use of a String to make a selection out of the group of available subjects, but various different means of selection can be imagined. Keep in mind what must happen during subject creation on the client side. Not only must a two-way, yin-yang pair of interpenetrating remote-object-and-proxy pairs be created, but the entire contents of the server-side subject must be transported to the client to start the replica off properly. From that point onward, only the changed parts come over the line.Hubs and tipsThe remote objects on the server and client sides are fundamentally different from each other: The server-side subject needs to maintain contact with a number of client-side representatives and propagate updates to them. Remember, the two participants are the hub and the tip. The hub lives on the server, and the tip lives on the client.The Hub interface is really of no interest to the application programmer — it only represents exactly how the client-side tip sees its hub. Hub is a typical RMI remote interface, and it closely resembles the Subject interface itself. The difference is that when a tip talks to its hub, the tip must identify itself. interface Hub extends Remote { void setRemote(Object theObject, int theIndex, int theTipNumber) throws RemoteException; void unlink(int theTipNumber) throws RemoteException; } The way the tip looks to the hub is a little simpler than the way the hub looks to the tip, with the setRemote() method basically mimicking the set() method of the Subject class. The only difference is that Subject.set() is called by the application code, while the Tip.setRemote() is a remote call originating on the server.interface Tip extends Remote { void setRemote(Object theObject, int theIndex) throws RemoteException; void unlink() throws RemoteException; } Both the Hub and Tip interfaces have unlink() methods, giving them the opportunity to break their partnership; these methods will be called when somebody uses the subject’s dispose() method.Another factoryWhen a new connection is to be built between a new tip and a possibly-existing hub, a request must be sent to the server to create (or find, depending on the application) a hub counterpart for the tip being created on the client side. The factory design pattern is an ideal way to code this sort of process, since it presents a way to hide the mechanics of instantiation. In this case, it also hides the tricky side-effect of setting up a bidirectional connection. This request is sent from the client-side SubjectFactory to the server-side HubFactory, which is an RMI remote object. It’s the only remote object that must be found using the Naming.lookup() method.The HubFactory has only one method, createHub, which receives the selection string and the tip’s proxy. It returns a HubLink which is a special serializable object encapsulating the link to the server used by the client-side tip.public interface HubFactory extends Remote { public HubLink createHub(String theName, Tip theTip) throws RemoteException; } A handy decoratorWhen a tip communicates with its hub counterpart, it must distinguish itself from the other tips so that the hub is able to broadcast the change message to everyone but the sender. However, since the application programmer sees only subjects (things that implement the Subject interface), there is no need for the tip itself to know which of the hub’s tips it represents. Knowledge of the different tip identities (with respect to their hub) is encapsulated in the HubLink class below. It is a decorator in the sense that it dresses up the set() call by adding tip identity to the call. class HubLink implements Serializable { ... private Hub hub = null; private int tipNumber = -1; ... public void set(Object theObject, int theIndex) throws RemoteException { if ( hub != null ) { hub.setRemote(theObject,theIndex,tipNumber); } } ... public void dispose() throws RemoteException { if ( hub != null ) { hub.unlink(tipNumber); } } ... } The HubLink is a serializable class because it actually begins its existence on the server, and is sent to the client during the establishment of a connection between hub and tip. The fields are private, indicating that this little server secret isn’t available to the client at all.Implementation: tipThe tip implementation must be seen as an implementor of the Tip interface from the point of view of the hub on the server, but it must also be seen as an implementor of the Subject interface from a local point of view. It is therefore a remote object that implements both interfaces.The TipImpl class is the representative of the subject that resides on the client. As you can see, it’s an RMI remote object, and it implements both the Tip interface (how it is seen by the server) and the Subject interface (how it is seen by the application). It makes method calls to the server exclusively through the HubLink decorator. class TipImpl extends UnicastRemoteObject implements Tip, Subject { ... private HubLink hubLink = null; ... public TipImpl(HubFactory theHubFactory, String theName) throws RemoteException { hubLink = theHubFactory.createHub(theName,this); } ... } The constructor of TipImpl makes use of a server entity called the HubFactory, which is able to provide it with new linkages to hubs. The actual container of the linkage is, of course, the HubLink decorator that is returned by the HubFactory.The tip must implement both a local and a remote mutator method so that changes in the subject’s attributes can originate either locally or remotely.class TipImpl ... { ... public void setRemote(Object theObject, int theIndex) throws RemoteException { if ( things.size() <= theIndex ) { things.setSize(theIndex+1); } things.setElementAt(theObject,theIndex); notifyWatchers(theIndex); } ... public void set(Object theObject, int theIndex) throws RemoteException { things.setElementAt(theObject,theIndex); notifyWatchers(theIndex); hubLink.set(theObject,theIndex); } ... } Clearly, both the setRemote() and the set() mutator methods must take the time to notify all of those local watchers that something has changed. The setRemote() method is called from the hub, and the set() method is called locally.Implementation: hubThe hub implementation must do a little more work than the tip, since it is responsible for broadcasting attribute changes to any number of tips. The code snippet below shows that proxies to the tips are stored in a vector and used when one of the mutator methods is called. class HubImpl extends UnicastRemoteObject implements Hub, Subject { ... private Vector tips = new Vector(); ... public void setRemote(Object theObject, int theIndex, int theTipNumber) throws RemoteException { things.setElementAt(theObject,theIndex); notifyWatchers(theIndex); for ( int walk=0; walk<tips.size(); walk++ ) { if ( walk != theTipNumber && tips.elementAt(walk) != null ) { try { ((Tip)tips.elementAt(walk)).setRemote(theObject,theIndex); } catch ( RemoteException remEx ) { unlink(walk); throw(remEx); } } } } } If something goes wrong during the propagation of the change to all the other tips, the RemoteException will cause the hub to first disconnect with that particular tip, and then inform the caller of the set() method.Who’s watching?On the client side, the watchers (implementors of the Watcher interface) probably will be user-interface components, and, of course, there can be more than one watching a particular subject. This means that any number of views could be paying attention to a subject at one time, even if they show very different aspects of the subject to the user.The server may employ a kind of scheduled persistency mechanism — when a subject has been changed, it (or perhaps a clone) can be put in a queue of things that will be stored in a persistent medium such as a relational database. The persistency mechanism would then be a watcher of the various subjects on the server.Dressed up for a demoTo demonstrate the different aspects of this approach to collaborative software, I’ve come up with a simple proto-game called Crawlies. The first step toward applying the subject pattern is to give it “clothing,” just as we might do in order to create a vector that pays attention to the type of its elements. For the game, we need to dress up the subject as “Crawly” with the following interface:public interface Crawly extends Watchable { // constants mapping elements to attributes int NAME = 0; int LOCATION = 1; int MESSAGE = 2; int SPEED = 3; int DIRECTION = 4; // accessors and mutators String getName(); Point getLocation(); void setLocation(Point theLocation) throws RemoteException; String getMessage(); void setMessage(String theMessage) throws RemoteException; int getSpeed(); void setSpeed(int theSpeed) throws RemoteException; Point getDirection(); void setDirection(Point theDirection) throws RemoteException; void dispose() throws RemoteException; } This interface really only accomplishes one thing: it defines the mapping between the subject’s elements and a number of type-safe attributes by means of accessors and mutators. Without the Crawly interface, we have a generic vector — but with it, we have a defined object with five different attributes.The implementor of this interface is a very simple decorator object called the CrawlySubject, which doesn’t do much more than implement the mapping the interface suggests. It simply maintains a private reference to an actual subject and feeds the method calls through. For example, the Location attribute’s accessor and mutator are shown here:public class CrawlySubject implements Crawly { ... private Subject subject; ... public Point getLocation() { return((Point)subject.get(LOCATION)); } ... public void setLocation(Point theLocation) throws RemoteException { subject.set(theLocation,LOCATION); } ... } It’s important to note that only one decorator class need be built, since it’s used on both the client and the server. This is a fine example of code re-use. Also, once the decorator class has been built, the programmer need no longer be concerned with the Subject interface and all that lies behind it.Another wardrobe?Needless to say, the bulk of the code required to set up a subject-oriented system with RMI is hidden from the rest of the application code by the decorator class — and it’s quite generic. As a consequence, the application of these techniques to a new software project really only requires that you build a new decorator class like CrawlySubject and an accompanying factory method. Both the decorator and its factory are basically trivial to create. There also is no real limitation on the number of different subject-based decorator objects within one application, so the infrastructure can function as a backbone for a number of different coexisting classes.LockingMany collaborative systems require that collisions be strictly avoided, but that characteristic has not been built into these classes explicitly yet. Locking can be a fairly tricky business. Basically, it allows a user to stake a temporary claim of editing rights on a subject so that nobody else can touch it for the time being. This can be as simple as defining a “locked” attribute of some kind, perhaps one that identifies the locking party to everyone. Two nearly simultaneous claims would result in a race to obtain the lock — but first-come, first-served would be an easy enough solution to this.ConclusionFor software to give people a shared experience, one essential feature must be provided: subjects must appear everywhere “live.” To achieve this, the subjects must be replicated on all client machines and changes must be propagated immediately and automatically with minimal network traffic. Java with RMI provides a convenient means by which the subject design pattern can be implemented.Gerald A. de Jong has a Java training and consultancy company called Beautiful Code BV in the Netherlands. When he’s not teaching or advising, he finds himself possessed by the spirit of R. Buckminster Fuller, working on his unique freeware Java structure-building program called Struck and dreaming about elastic interval geometry. He hopes to one day populate virtual worlds with evolving digital biota. Web Development