Abstract. Creation of a remote object often takes a long time due to both potential database queries and object registration requirements defined by the distributed object technology. For example, in a CORBA environment a remote object must register itself with the ORB before it can be exported. Long database queries combined with potentially slow object registration can cause a rather long delay to occur between the time that a client issues a request and the time that the request is answered. CORBA, detailed in Chapters 22 through 33, is a technology that allows code written in multiple languages, running on multiple machines to communicate and share processing. CORBA technology is taking the computing world by storm, and is one of this book’s major focuses.
In addition to long creation time, server objects take up memory; if the server runs out of memory, it could crash the whole system. Assuming a stateless remote object, it is possible to take this creation hit once and then allow clients to share the instance.
Note When talking about remote objects, the terms statefull and stateless refer to the remote object’s capability to be changed by a client. When a method is invoked on a statefull object, that object’s member data can change. When a method is invoked on a stateless object, none of that object’s member data
changes.
Problem. In a distributed environment, speed is an issue that developers must constantly consider. Method invocations on a distributed object involve network traffic, and server implementations must deal with the fact that clients could create thousands of objects at the server. All of these server objects take up memory, and can take a long time to instantiate. If possible, the number of server objects created should be minimized, thus allowing for reduced resource usage and reduced response time to client queries.
Context. This pattern has applications in any distributed environment where multiple clients need access to the same server object. In general, this object must be stateless.
In some situations it may be possible for clients to share a statefull object, but much care has to be taken to ensure that dirty data is never seen.
Note Dirty data refers to data that has been altered by one client and that another client thinks contains historical information. For example, if two clients reference the same remote object and one alters the remote object’s data, that new data is called dirty until everyone references the same information.
Forces. The shared instance pattern achieves its greatest success when a single server object is shared by lots of clients. If a server object is only going to be occasionally used by one or two clients, it is not worth the work required to track usage.
Solution. Serving shared instances to multiple clients places three major requirements on the server developer. First of all, the server must be able to ensure that two queries are identical. The second requirement comes into play once a query is identified as already executed. At this point, the server must be able easily locate the unique response objects. If either of these first two requirements is ignored, it is quite possible that the wrong query results will be returned to the user, which could be a major security risk. The final requirement that must be heeded by developers involves identifying when no clients have access to the shared server object. At some point, the server must be able to destroy the shared object and the only safe time to do this is when no clients are accessing the object.
The first requirement of uniquely identifying queries can easily be achieved by
aggregating query parameters into a holder class and overloading that class’s equals() method. If the query does not accept any parameters, you need not bother with this requirement. Listing 5.9 shows an example query holder class that might be used for searching a database of people.
LISTING 5.9 THE PersonQuery CLASS
public final class PersonQuery { private String _sFirstName;
private String _sMiddleName;
private String _sLastName;
// getter methods
public String getFirstName() { return _sFirstName; } public String getMiddleName() { return _sMiddleName; } public String getLastName() { return _sLastName; }
// setter methods
public void setFirstName(String sFirstName) { ẻ _sFirstName = sFirstName; }
public void setMiddleName(String sMiddleName) { ẻ _sMiddleName = sMiddleName; }
public void setLastName(String sLastName) {
ẻ _sLastName = sLastName; }
// overload equals()
public boolean equals(Object compare) {
// make sure that a PersonQuery object was passed if(!(compare instanceof PersonQuery)) return false;
PersonQuery personCompare = (PersonQuery)compare; //
ẻ cast once to save time // check all fields
if(! personCompare.getFirstName().equals(getFirstName()))
ẻ return false;
if(!
personCompare.getMiddleName().equals(getMiddleName())) ẻ return false;
if(! personCompare.getLastName().equals(getLastName())) ẻ return false;
return true; // all good }
}
Once a query has been identified as unique, a server object must then decide if the object to be served in response already exists. To facilitate discovery of the response objects, a hashtable that uses the query as a key can be used.
All query results in Listing 5.10 are stored in a hashtable. The query object is used as the key and the query results are used as a value.
LISTING 5.10 THE SharedInstance CLASS
import java.util.*;
public class SharedInstance {
private Hashtable _hshResults;
public SharedInstance() {
_hshResults = new Hashtable();
}
public Person[] executeQuery((PersonQuery query) { // check if the query has already been performed if(_hshResults.containsKey(query)) return
ẻ _(Person[])hshResults.get(query);
// query has not been performed
Person[] returnValue == // get from database
// register the return value with the distributed object
ẻ technology
_hshResults.put(query, returnValue); // store the results
return returnValue; // return the results }
}
It is easy for Java programmers to forget about that time long, long ago when they actually had to properly dispose of allocated memory. Java does provide a garbage collector that usually destroys any allocated unused object, but functionality is not always the same in a distributed environment. Unless the distributed object technology explicitly provides a distributed garbage collector, you as the developer are charged with this responsibility. If a remote object is given to a single client, it is easy to tie the lifecycle of the remote object to the lifecycle of the client. In situations where the remote object is shared between many clients, that number of clients must be explicitly tracked.
Listing 5.11 expands on Listing 5.10 to include support for something called reference counting. Reference counting involves tracking client references to a server object. When an object is served to a client, that object’s reference count is incremented by 1. When the client is done with the remote object, that object’s reference count is decreased by 1.
When an object’s reference count is 0, the server can destroy the object.
LISTING 5.11 SharedInstanceWithReferenceCounting AND PERSON CLASSES
import java.util.*;
public class SharedInstanceWithReferenceCounting { private Hashtable _hshResults;
public SharedInstanceWithReferenceCounting() { _hshResults = new Hashtable();
} /**
* Adds one to each object"s reference count */
private void addToReferenceCount(Person[] persons)) { int iLength = persons.length;
for(int i=0; i<iLength; i++) {
persons[i].addToReferenceCount();
} } /**
* Adds one to each object"s reference count */
private void subtractfromReferenceCount(Person[] persons)) { int iLength = persons.length;
for(int i=0; i<iLength; i++) {
if(persons[i].subtractfromReferenceCount()) ẻ destroyObject(persons[i]);
} } /**
* Does any needed clean-up and destroying of objects
*/
private final void destroyObject(Person person) { // destroy
}
public Person[] executeQuery((PersonQuery query) { Person[] returnValue == null;
// check if the query has already been performed if(_hshResults.containsKey(query)) returnValue = (Person[])
ẻ _hshResults.get(query);
else returnValue = // get from database
// register the return value with the distributed object
ẻ technology
// add to the reference count addToReferenceCount(returnValue);
_hshResults.put(query, returnValue); // store the results
return returnValue; // return the results }
/**
* Invoked by the client to indicate that he is done with all
ẻ objects */
public void doneWithObjects(Person[] persons)) { subtractfromReferenceCount(persons);
} }
class Person {
private int _iRefCount = 0;
public Person() { }
/**
* Adds one to the reference count */
public void addToReferenceCount() { _iRefCount++;
} /**
* Subtracts one from the reference count.
*
* @return true If the refernce count is zero after subtraction
* @return false If the reference count is non-zero after
ẻ subtraction */
public boolean subtractfromReferenceCount() { _iRefCount";
return (_iRefCount == 0);
} }
Examples. In covering this pattern, code examples were provided in the solution section.
These examples taken together provide for a working example of this pattern.
Resulting Context. Use of this pattern has obvious memory implications at the server level. Users must employ reference counting (as discussed earlier) to ensure that objects are destroyed when no longer in use.
Rationale. The shared instance pattern provides a method to speed delivery of remote objects to clients. Objects that take a long time to create can be created only once and then shared between multiple clients.
Known Uses. As with all other patterns covered in this chapter, the shared instance pattern is employed in many of the solutions that I have developed in the past few years. Most recently, I developed a piece of software to view hospital patient records. All incoming data was fed into the system via legacy system gateways, and my software provided read-only access to the data. Creating the graph of objects that compromised a single record was an involved process that took around four seconds. Through use of the shared instance pattern, I was able to reuse the same objects with multiple clients.
FROM HERE
The software industry is definitely heading toward a lot of trouble in the next few years.
Unless all computers shut down in the year 2000, we will soon find ourselves forced to develop amazing feature-rich applications in hardly any time at all. Only by using patterns and other development technologies will we be able to rapidly develop the needed software.
As you continue your exploration, the following chapters aid in your understanding of the material in this chapter:
• Chapter 6, “The Airline Reservation System Model,” uses the UML use-case notation to describe an airline reservation system.
• Chapter 15, “Remote Method Invocation (RMI),”describes a Java core technology that provides a simple but effective object bus for Java objects.
• Chapter 21, “CORBA Overview,” provides a solid description of what CORBA is and how it can be used in your applications.
Chapter 6: The Airline Reservation System Model
Overview
Contained within the pages of this book is detailed information on five different
technologies that allow for distributed communication between applications. Even though all of these technologies enable this level of communication, each takes a radically different approach. Further complicating matters is the fact that there is no one perfect distributed object technology for all development efforts; only a careful analysis of the
problem and a detailed knowledge of the technologies shows a best solution.
Given that choosing the best distributed object technology can often make or break a project, this book spends significant time teaching you how to make this decision. This knowledge is imparted in two complementary manners. First a discussion of the differences between the competing technologies is presented, and then you use each technology to implement a common piece of software.
This chapter presents a formal definition of that common piece of software, an airline reservation system. The definition is then implemented throughout the book in chapters immediately following coverage of each technology. The formal definition of the airline reservation system is presented using the Unified Modeling Language (UML), which is covered in Chapter 3, “Object-Oriented Analysis and Design.” Chapter 3 provides a solid introduction to the UML. This chapter does, however, spend some time covering aspects of the UML that apply to use-cases and activity diagrams.
Note The UML is a tool used to formally model the entire software process. By using the UML you can model a development effort from the beginning of the requirements gathering to the final implementation models. The UML is quickly becoming the de facto standard for modeling software, and proficiency in it will most likely become an essential job skill in the next five years.