HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

Some of the power of object-oriented design comes from abstraction, which allows for a separation of concerns between classes and their derivative parts. The general concerns of a given class are handled by the base class. Specific classes are derived from the base class to add differing behavior or functionality. Along the same lines, other classes are typically defined to manage these objects by their base class and know nothing of the details of the derived class. At some point, another object is going to want to use the derived form of the generic objects.

If the kind of object required is known, and an object's identity can be determined, then the object can be promoted through casting. In Java, it is easy to determine the class of an object because that capability is built into the language. Unfortunately the methods used to manage the promotion of objects can be problematic. One mistake in dealing with this situation is to let each class identify and promote the general class itself. With checks and casts in numerous places the code becomes cluttered, and hard to read and maintain.

A more palatable solution is to isolate the checks and casts to a single class that is responsible for taking care of these chores. It certainly is a better solution, but it too has drawbacks. For instance, this single class now must hold knowledge of every object to be promoted. This also means the interface of this class can become bloated if many object promotions are involved.

There is yet another solution. The solution described here uses an example modelled after a real problem the author solved for a client: message passing between objects. The difference is that the implementation language was C++, not Java. C++ supports multiple inheritance, so a designer can make statements like: an object X is a kind of object Y and a kind of object Z. Assuming the relationships between X, Y and Z are not dichotomous, modelling in this way is perfectly legitimate. Support for object promotion becomes part of the class itself.

Java, on the other hand, supports only single inheritance, but it does have an interface construct. This required the design to be based more on using relationships than kinds of relationships. However, this was not a limitation; if anything, the interface construct made the relationships and design easier to understand.

Some basic assumptions about message handling are that each message is complex enough to warrant a message class hierarchy. The message handlers know only about objects of type Message, the general class; but objects receiving those messages need to know about the leaf class for it to be useful and type safe.

For lack of a better term, the mechanism defined here is called the Object-Producer-Consumer Idiom because the Object, Producer and Consumer are the three most important abstractions to making this mechanism work. The Object in this case is a Message. The abstractions for the mechanism are defined as follows:

Message is the message base class; all messages are derived from Message.

A consumer of Messages is any object defined to consume Message objects. More accurately, a MessageConsumer is defined to consume particular kinds of messages. This provides the type safety and automatic understanding of the kind of message received.

A MessageReceiver receives messages and presents them to the MessageConsumer through the services of the MessageProducer.

A MessageProducer takes a Message and converts it to its appropriate leaf class. At this level a MessageProducer is actually abstract. At the lowest level, every participating Message of type X needs an associated MessageProducer of type X and a MessageConsumer interface of type X.

Now it becomes apparent that for every Message X, two other things need to be defined: its Producer class and its Consumer interface. Fortunately both are trivial; unfortunately, Java has no template support. Template support would remove some of the drudgery of creating these cookie cutter classes with the only difference between them being their types. The other worry might be class explosion, but the author discounts this concern mostly because the benefits outweigh the perceived problem.

Taking a look at the source listing, we have the following:

Section 1 of the source listing shows the definition for the base class of every Message and MessageProducer. Note that the MessageProducer is abstract and defines a method for determining if it is associated with a given Message object and a method to process the Message. For more sophisticated lookup by the MessageReceiver, a method that returns the Class might be substituted for the simple handles() query.

Section 2 shows the definitions of the specific message, MessageX, and its associated definitions for the MessageProducerX class and MessageConsumerX interface. The MessageProducerX takes a Message ConsumerX as its construction parameter binding the two tightly together as seen in Section 4.

Two other sets of associated definitions for MessageY and MessageZ were left out of the example, but will be used in Section 5. There are identical to the MessageX set with the types changed.

Section 3 defines the MessageReceiver. It too is a trivial class and implements only two methods, one to add an additional MessageProducer, and one to post messages.

Section 4 defines the MessageConsumer, which declares that it implements both the MessageConsumerX and MessageConsumerY interfaces. As part of its construction it takes a MessageReceiver as an argument, so that messages can be posted directly from the test code. The MessageConsumer simply displays a text string when it receives a message of a particular type. Also, as part of its construction, it constructs a MessageProducer for each Message it wants to receive, and adds it to the MessageReceiver. When a MessageProducer is constructed, this (the MessageConsumer) is given as the construction parameter so that it knows which object to plug into through its interface.

Finally, Section 5 is the main() program used to test the mechanism. The MessageReceiver is given three messages, MessageX, MessageY and MessageZ. When the program runs it prints out that it received MessageX and MessageY, but since the MessageReceiver was not setup to receive messages of class MessageZ, it is simply ignored.

In summary the mechanism works as follows. A consumer of messages is defined as a consumer of some set of messages through the use of interfaces. The consumer links itself to a MessageReceiver by creating MessageProducers for each kind of message it wants to receive, and gives them to the MessageReceiver. Each of the Producers are tightly bound to the consumer through the the consumer message interface and a reference to consumer. When a MessageReceiver gets a Message, it looks for an appropriate MessageProducer and if found, gives the Message to the Producer. If it is not found, then this implementation simply ignores the message. Once the Producer has the message it performs the promoting cast, and calls the consumer through its interface. The consumer receives the Message of a particular type through a redefined method, specified via the consumer interface for the particular Message. And that is it in a nut shell.

This example used messages as objects, but this technique can be used for any class of object where one could benefit from the Object-Producer-Consumer Idiom. Some of its benefits are that it completely does away with hardcoded switches otherwise required, provides a type safe and controlled interface, and allows for dynamic registration of participating objects. For Java programs that dynamically load classes, the last item could be quite useful.

About the Author
Nathan Whelchel's career in software engineering began in 1978 when he taught himself to program. In 1987 he took an interest in object-oriented methods and languages, and began practicing religiously. In 1990, he struck out on his own, started Cyber-Nexus, Ltd. and began consulting, using his experience in object-oriented techniques and programming in C++. In the beginning of 1996, Nathan resolved to write tools for developing object-oriented programs. He chose Java over C++ as the implementation language because he liked the language features, flexibility, and its role on the Internet. Nathan can be reached at [email protected]


Program Listing

import java.util.Vector;

// Section 1:      Classes to support MessageX

class Message { 	// extends Object whos identification capabili
		// ties are used has whatever other general
		// capabilities a Message might have

abstract class MessageProducer {
    public abstract boolean handles(Message m);
		// return true can handle message

    public abstract void produce (Message m);   
		// interface to produce the message

//================================================================ // Section 2: Classes to support MessageX //================================================================ class MessageX extends Message { } //Some message class // Any object that gets a MessageX implements an // interface to consume a MessageX. interface MessageConsumerX { public abstract void consume (MessageX m); } class MessageProducerX extends MessageProducer { private MessageConsumerX consumer; public MessageProducerX (MessageConsumerX c) { consumer = c; } public boolean handles(Message m) { return ( m instanceof MessageX); } public void produce (Message m) { consumer.consume ((MessageX)m); } } // Classes to support MessageY and MessageZ removed for brevity // but they are exactly like those for MessageX with the type // changed

//================================================================= // Section 3 The MessageReceiver uses general abstractions // only //=============================================================== class MessageReceiver { private Vector producers = new Vector(); public void add (MessageProducer producer) { producers.addElement (producer); } public void postMessage(Message m) { for (int i=0; i < producers.size(); i++) { MessageProducer producer = (MessageProducer)producers.elementAt(i); if (producer.handles(m)) { producer.produce(m); break; } } } }

//=============================================================== // Section 4 The MessageConsumer defines which messages to // process the interface to process thhose message once received //=============================================================== class MessageConsumer implements MessageConsumerX, MessageConsumerY { private MessageReceiver receiver; public MessageConsumer (MessageReceiver r) { receiver = r; receiver.add (new MessageProducerX(this)); receiver.add (new MessageProducerY(this)); } public void consume (MessageX m) { System.out.println("Received a MessageX"); } public void consume (MessageY m) { System.out.println("Received a MessageY"); } }

//================================================================ // Section 5 main() to do a simple test of the code //================================================================ class TestProducerConsumer { static public void main (String[] args) { MessageReceiver receiver = new MessageReceiver(); MessageConsumer consumer = new MessageConsumer(receiver); receiver.postMessage(new MessageX()); receiver.postMessage(new MessageY()); receiver.postMessage(new MessageZ()); } }


All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.