Components and Java

I l @ ve RuBoard

Components and Java

First, there was structured design and programming, which promoted the ideas of abstraction and encapsulation but was unable to enforce them effectively. Then came object-oriented development, which was able to implement these concepts but sometimes did so clumsily. Finally, there came component-oriented development, whereby developers no longer built ordinary classes but instead created "components." But what distinguishes a component from a class, and which camp do Java, J#, and .NET fall into?

The problem with classes is that, well, they're not components. To understand this point, ask yourself a couple of questions: What goes in a class? What is the purpose of a class? Typically, a class contains some private data and some public methods that a consumer can call to query or manipulate the private data. A class might contain some private methods as well, or even some public data (ugh!), but we'll ignore those for now. The purpose of a class is to model some object in the real world.

At issue is the way in which you interact with objects in the real world. You can ask an object to perform an action, and it will do it if it understands the request. How it performs the action shouldn't matter to you as long as it does it. Methods are good for implementing this sort of behavior in computer programs that model objects. However, most objects also have properties ”values that you can inquire about or change. A bank account object might have a current balance property, for example. Given that public data is often considered a bad thing, you should use methods to implement properties because they can be used to provide access to data values while vetoing any changes that violate business rules. However, the drawback of using methods in this case is that you generally need two of them for each property ”one to ask about the current value and one to change it. These are referred to as accessor methods, or getters and setters.

Another aspect of object modeling that's often ignored by the classic object-oriented view of the universe is that sometimes objects need to tell the outside world that something significant has happened . For example, the nuclear power station core object might find it necessary to inform the safety-valve object that it is overheating ! In other words, objects need to be able to raise events. Objects must also be able to receive events ”it's no good shouting if no one is listening.

Component-oriented development addresses these issues. A component is a self-contained object that can interact with the world in a well-defined manner by exposing methods, properties, and events.

JavaBeans

The JDK includes the JavaBeans framework for creating and consuming components. A JavaBean is essentially just a Java class that exposes properties and events as well as methods. However, when the JavaBeans framework was proposed, one requirement was to not change or extend the JVM. As a result, the JavaBeans framework relies on specific naming conventions to indicate to Java ­Beans-aware tools (such as the BeanBox supplied with the Bean Development Kit [BDK]) that a particular item is a property or an event.

For example, properties are indicated by implementing a pair of methods called getXXX and setXXX , where XXX is the name of the property and read-only properties are defined by creating a getXXX method but no setXXX method. Similarly, a JavaBean that publishes events implements addYYYListener and removeYYYListener methods, where YYY is the name of the event. These methods allow other classes to subscribe to and unsubscribe from the YYY event. Subscribing classes must also implement the java.util.EventListener interface (which is just a tag interface). When a JavaBean raises an event, it creates an event state object to hold any information needed by the event subscribers ”this is a class that inherits from java.util.EventObject . The event state object is then passed to each subscribing object for processing.

Development tools that understand JavaBeans can use reflection to examine the structure of a bean and discover its properties and events. ( Reflection is a technique that allows an object to examine the internal structure of classes.) Many JavaBeans are graphical components, and tools such as the BeanBox (shown in Figure 3-1) allow a developer to drag and drop them onto a form, set their properties, and wire them together automatically using events. Most graphical JavaBeans also provide editors, which allow a developer to set complex properties at design time.

Figure 3-1. The BDK BeanBox

Ordinary Java classes can also use JavaBeans, but the process is more akin to consuming an ordinary class ”you should use the setXXX and getXXX methods explicitly to access properties, for example.

Business-Oriented JavaBeans

You can build JavaBeans that act as business components. The OvenBean class shown below (which is available in the JDK OvenBean folder) is a JavaBean that models a simple kitchen oven. It exposes a property called Temperature , which is accessible through the setTemperature and getTemperature methods, and an event called TemperatureEvent that is raised whenever the oven gets too hot or cold. The TemperatureEvent state object contains information about the current temperature of the oven and is passed to each subscriber when the event is raised. A class subscribes to the TemperatureEvent using the addTemperatureEventListener method and can unsubscribe using the removeTemperatureEventListener method. The signatures and naming conventions used by the property and event are those mandated by the JavaBeans framework. The method fireTemperatureEvent actually raises the TemperatureEvent and is called by the setTemperature method if the requested temperature is outside the operational bounds of the oven. Each subscriber must implement the TemperatureEventListener interface (shown later), which specifies a method called handleOvenEvent . The fireTemperatureEvent method invokes the handleOventEvent method in each subscriber in turn .

OvenBean.java
 importjava.util.*; //JavaBeanthatmodelsasimpleovenforbakingcakes. //OvenBeanexposesapropertytoget/settheoventemperature //OvenBeanraisesaTemperatureEventiftheovenoverheatsorunderheats publicclassOvenBean { //Constantsthatdefinetheworkingtemperaturerangeoftheoven publicstaticfinalintLOW_OVEN_TEMP=100; publicstaticfinalintHIGH_OVEN_TEMP=500; //Currenttemperatureoftheoven privateinttemperature=LOW_OVEN_TEMP; //Eventsubscribers privateVectorlisteners=newVector(); //SubscribetotheTemperatureEvent publicvoidaddTemperatureEventListener(TemperatureEventListenerl) { listeners.addElement(l); } //UnsubscribefromtheTemperatureEvent publicvoidremoveTemperatureEventListener(TemperatureEventListenerl) { listeners.removeElement(l); } publicvoidfireTemperatureEvent() { VectorlistenerCopy; //Nolisteners-donothing if(listeners.isEmpty()) return; //Otherwisebuildastateobjectholdingthecurrenttemperature TemperatureEventte=newTemperatureEvent(this,temperature); //createacopyofthelisteners'vector(topreventraceconditions) synchronized(this) { listenerCopy=(Vector)listeners.clone(); } //iteratethroughthecopy,invokingeachlistener's "action" method for(Enumerationl=listenerCopy.elements();l.hasMoreElements();) { ((TemperatureEventListener)(l.nextElement())).handleOvenEvent(te); } } //Methodsforgettingandsettingtheoventemperature publicintgetTemperature() { returntemperature; } publicvoidsetTemperature(intnewTemperature) { temperature=newTemperature; //Ifthenewtemperatureisoutsidetheworkingrangeoftheoven, //raiseaTemperatureEvent if(newTemperature<LOW_OVEN_TEMPnewTemperature> HIGH_OVEN_TEMP) fireTemperatureEvent(); } } 

The TemperatureEvent state object, shown in the TemperatureEvent.java code that follows , obeys the requirement that all event state objects must inherit from java.util.EventObject . The EventObject constructor (which you should call from any constructor in the state object) requires a reference to the object raising the event. The only other data held in the state object is the current temperature of the oven.

TemperatureEvent.java
 importjava.util.*; //TheeventstateobjectfortheTemperatureEvent publicclassTemperatureEventextendsEventObject { privateinttemperature; publicTemperatureEvent(Objectsource,inttemperature) { super(source); this.temperature=temperature; } publicintgetTemperature() { returntemperature; } } 

A subscribing class must implement the TemperatureEventListener interface shown in TemperatureEventListener.java below. The TemperatureEventListener extends the java.util.EventListener tag interface required of all event listener objects. The TemperatureEventListener interface defines the handleOvenEvent method, which is executed when the TemperatureEvent is raised by the OvenBean class.

TemperatureEventListener.java
 importjava.util.*; publicinterfaceTemperatureEventListenerextendsEventListener { //DefinethesignatureofthemethodthathandlestheTemperatureEvent publicvoidhandleOvenEvent(TemperatureEventte); } 

The TemperatureWarning class shown next implements the Temperature ­EventListener interface. This class can subscribe to the TemperatureEvent published by the OvenBean class.

TemperatureWarning.java
 importjava.util.*; //SubscriberfortheTemperatureEvent publicclassTemperatureWarningimplementsTemperatureEventListener { //ImplementthemethodthathandlestheTemperatureEvent publicvoidhandleOvenEvent(TemperatureEventte) { System.out.println("TemperatureEventraised.Temperatureis " + te.getTemperature()); } } 
JavaBeans and J#

The OvenBean example was originally developed using the JDK 1.1.4, and you can compile it to Java class files using the javac compiler. However, you can also compile the classes, unchanged, into an assembly using the J# compiler. The following command will create OvenBean.dll:

 vjc/target:library/out:OvenBean.dllOvenBean.javaTemperatureEvent.java TemperatureEventListener. javaTemperatureWarning.java 

Using ILDASM will confirm that it contains the OvenBean , Temperature ­Event , TemperatureWarning and TemperatureEventListener classes, as shown in Figure 3-2.

Figure 3-2. ILDASM displaying the contents of OvenBean.dll

The BeanClient class is a simple test harness for OvenBean . It creates an OvenBean and a TemperatureWarning object. The listener object subscribes to the TemperatureEvent . The program makes several calls to setTemperature . The first call (150) will not raise an event, but the two calls that follow (99 and 501) will change the oven temperature to values outside the valid range. Both of these calls will result in the TemperatureEvent being raised, and the listener object will respond by running its handleOvenEvent method.

BeanClient.java
 publicclassBeanClient { publicstaticvoidmain(String[]args) { //CreateanOvenBean OvenBeanob=newOvenBean(); //Createaneventlistener-fortesting TemperatureEventListenertel= newTemperatureWarning(); //SubscribetotheTemperatureEvent ob.addTemperatureEventListener(tel); //Testtheovenbysettingitstemperaturetodifferentvalues ob.setTemperature(150);//Shouldnotraiseanevent ob.setTemperature(99);//Shouldraiseanevent ob.setTemperature(501);//Shouldraiseanevent } } 

Again, this program will compile using the javac compiler. You can also compile the program using the J# compiler and link it with the OvenBean DLL using the following command:

 vjcBeanClient.java/reference:OvenBean.dll 

If you run the program BeanClient.exe, you'll see the output shown in Figure 3-3.

Figure 3-3. The output of the BeanClient program

Components in .NET

You can see that the JavaBeans framework allows you to create graphical and business components using the Java language and the JDK, and you can also use this framework from J#. But the .NET Framework has an arguably more elegant architecture for publishing events and properties.

Defining Properties

Properties are a built-in feature of the common language runtime. Properties are implemented in a similar manner to properties in JavaBeans. You can access a property by using a pair of accessor methods. The naming rules are slightly different, though; the get accessor must be called get_XXX (where XXX is the name of the property ”note the underscore in the name), and the set accessor must be called set_XXX . Furthermore, both accessors must be tagged with the /** @property */ directive, as shown in the following example, which is from the J# implementation of the OvenBean component in the NET OvenBean folder:

 publicclassOvenBean { //Constantsthatdefinetheworkingtemperaturerangeoftheoven publicstaticfinalintLOW_OVEN_TEMP=100; publicstaticfinalintHIGH_OVEN_TEMP=500; //Currenttemperatureoftheoven privateinttemperature=LOW_OVEN_TEMP; //TheTemperatureproperty /**@property*/ publicintget_Temperature() { returntemperature; } /**@property*/ publicvoidset_Temperature(intvalue) { temperature=value; } } 

To access a property from a J# client, you use the regular method call syntax. The following code is from the ComponentClient class, which is also in the .NET OvenBean folder:

 publicclassComponentClient { publicComponentClient() { //CreateanOvenBean OvenBeanob=newOvenBean(); //Testtheovenbysettingitstemperaturetodifferentvalues ob.set_Temperature(150);//Shouldnotraiseanevent ob.set_Temperature(99);//shouldraiseanevent ob.set_Temperature(501);//shouldraiseanevent } } 

The syntax for accessing a property from other languages such as C#, however, uses direct assignment (the same syntax you use when you access a variable), as shown below. Assigning to a property causes the set accessor to be invoked, and reading a property executes the get accessor. When the J# OvenBean class is compiled, the /** @property */ directive causes metadata to be generated for the property methods, allowing them to be called in this fashion from other languages.

 //C# OvenBeanob=newOvenBean(); ob.Temperature=150;//callsob.set_Temperature(150) inttp=ob.Temperature;//callsob.get_Temperature() 

Note

This syntactic feature is also true of properties belonging to classes defined in the .NET Framework Class Library ”you'll always have to use set_XXX and get_XXX methods when you access properties from J# code. Fortunately, the IntelliSense feature of Visual Studio .NET generates the correct syntax for you automatically.


Defining Events and Delegates

The .NET model of events is based on delegates. A delegate holds a reference to a method ”a little like a function pointer or a callback if you're familiar with C++. However, unlike function pointers, delegates are type-safe; a delegate specifies a method signature, and it can be used only to refer to a method that has a matching signature. For example, the TemperatureEventHandler delegate shown in the following code (taken from the J# OvenBean class) specifies that any referenced method should not have a return value and must take two parameters ”an Object and a TemperatureEventArgs (which we'll describe shortly). Delegates must be tagged with the /** @delegate */ directive for the J# compiler to recognize them and generate the metadata needed by the common language runtime. If you omit this directive, the code will not compile.

 publicdelegatevoidTemperatureEventHandler(System.Objectsender, TemperatureEventArgsargs); 

Declaring a delegate in this way creates a new type of object. You can then create a delegate variable using this type:

 privateTemperatureEventHandleronTemperatureEvent=null; 

Once a delegate variable has been defined, it can be used to hold references to zero or more methods. When the Invoke method of the delegate variable is executed, all of the referenced methods will be called ”this is how you raise an event. You should contrast this operation with the manual event-firing mechanism used by the JavaBeans example you saw earlier. For example, the OnTemperatureEvent method shown in the following code raises the TemperatureEvent by invoking the onTemperatureEvent delegate variable. The parameters passed to the Invoke method will be forwarded on to each method pointed to by the delegate variable. (It is conventional for the first parameter to be a reference to the object raising the event and for the second parameter to contain any additional data required to handle the event, as described in the next section.)

 protectedvoidOnTemperatureEvent(TemperatureEventArgsargs) { if(onTemperatureEvent!=null); onTemperatureEvent.Invoke(this,args); } 

You must provide a mechanism to allow clients to subscribe to or unsubscribe from an event. You achieve this by providing a pair of methods that can add a subscribing method to the delegate or remove a method from the delegate. In J#, these methods must be called add_XXX and remove_XXX , where XXX is the name of the event. These methods must take a delegate variable as a single parameter that refers to the method to be added or removed. (The client will create and populate this variable.) Furthermore, both methods must be marked with the /** @event */ directive so the compiler can generate the appropriate metadata to allow clients written in other languages to subscribe to the event. In the following code, note the use of the static methods Delegate.Combine and Delegate.Remove to add or remove a delegate from a delegate variable. The result of these operations is a delegate variable with the new delegate added or removed, which is then assigned to the private onTemperatureEvent variable. A cast is necessary because the Combine and Remove methods return a generic Delegate object.

 //SubscribetotheTemperatureEvent /**@event*/ publicvoidadd_TemperatureEvent(TemperatureEventHandlerhandler) { onTemperatureEvent= (TemperatureEventHandler)Delegate.Combine(onTemperatureEvent,handler); } //UnsubscribefromtheTemperatureEvent /**@event*/ publicvoidremove_TemperatureEvent(TemperatureEventHandlerhandler) { onTemperatureEvent= (TemperatureEventHandler)Delegate.Remove(onTemperatureEvent,handler); } 
Event Arguments

We mentioned earlier that when you raise an event, it is conventional for the delegated method to take two parameters. The first parameter is a reference to the object raising the event (passed as an Object ), and the second contains any additional information needed by the subscriber to handle the event ”the equivalent of the event state object in the JavaBeans model. A further convention is that the second parameter should subclass the System.EventArgs class. (Your code will still work if you don't do this, but it is considered poor practice if you don't.) The TemperatureEvent raised by the J# OvenBean class passes the temperature of the oven to subscribers. The TemperatureEventArgs class shown below wraps the temperature in an EventArgs object:

 publicclassTemperatureEventArgsextendsEventArgs { privateinttemperature; publicTemperatureEventArgs(inttemp) { temperature=temp; } /**@property*/ publicintget_Temperature() { returntemperature; } } 

The TemperatureEvent is raised by the set_Temperature accessor method when the oven becomes too hot or too cold. The method creates a new TemperatureEventArgs object containing the requested temperature and then calls the OnTemperatureEvent method to invoke the subscribing delegates. The set_Temperature method is shown here ”you saw an abbreviated version of this method earlier.

 /**@property*/ publicvoidset_Temperature(intvalue) { temperature=value; if(temperature<LOW_OVEN_TEMPtemperature>HIGH_OVEN_TEMP) OnTemperatureEvent(newTemperatureEventArgs(temperature)); } 

A complete listing of the OvenBean class is shown below. You might find it useful to compare it to the JDK implementation covered earlier.

OvenBean.jsl
 packageOvenBean; importSystem.*; publicclassOvenBean { //Constantsthatdefinetheworkingtemperaturerangeoftheoven publicstaticfinalintLOW_OVEN_TEMP=100; publicstaticfinalintHIGH_OVEN_TEMP=500; //Currenttemperatureoftheoven privateinttemperature=LOW_OVEN_TEMP; //Delegatedefiningthesignatureoftheeventcallback /**@delegate*/ publicdelegatevoidTemperatureEventHandler(Objectsender, TemperatureEventArgsargs); //Theeventitself,declaredasadelegate privateTemperatureEventHandleronTemperatureEvent=null; //SubscribetotheTemperatureEvent /**@event*/ publicvoidadd_TemperatureEvent(TemperatureEventHandlerhandler) { onTemperatureEvent=(TemperatureEventHandler)Delegate.Combine(onTemperatureEvent,handler); } //UnsubscribefromtheTemperatureEvent /**@event*/ publicvoidremove_TemperatureEvent(TemperatureEventHandlerhandler) { onTemperatureEvent=(TemperatureEventHandler)Delegate.Remove(onTemperatureEvent,handler); } //RaiseTemperatureEvent protectedvoidOnTemperatureEvent(TemperatureEventArgsargs) { if(onTemperatureEvent!=null); onTemperatureEvent.Invoke(this,args); } //TheTemperatureproperty /**@property*/ publicintget_Temperature() { returntemperature; } /**@property*/ publicvoidset_Temperature(intvalue) { temperature=value; if(temperature<LOW_OVEN_TEMPtemperature>HIGH_OVEN_TEMP) OnTemperatureEvent(newTemperatureEventArgs(temperature)); } 

}

You can compile the OvenBeans.jsl and TemperatureEventArgs.jsl files into a DLL (OvenBean.dll) and reference the component from other applications:

 vjc/target:library/out:OvenBean.dllOvenBean.jslTemperatureEventArgs.jsl 
Consuming a .NET Component

When you use a .NET component, the J# compiler maps properties and events into Java-language constructs. The ComponentClient class is a test program that exercises the J# OvenBean component.

The handleOvenEvent method matches the signature of the Temperature ­EventHandler delegate and is used for handling the TemperatureEvent when it is raised. All the method actually does is print out the temperature of the oven, which is passed in through the TemperatureEventArgs parameter:

 System.out.println("TemperatureEventraised.Temperatureis " + args.get_Temperature()); 

The bulk of the work is performed in the ComponentClient constructor. It instantiates an OvenBean object using regular Java syntax. The constructor then creates a TemperatureEventHandler delegate based on the handleOvenEvent method:

 OvenBean.TemperatureEventHandlerhandler= newOvenBean.TemperatureEventHandler(handleOvenEvent); 

This delegate is used to subscribe to the TemperatureEvent of the OvenBean object:

 ob.AddOnTemperatureEvent(handler); 

Finally, the temperature of the OvenBean is set to different values. Again, notice how the Temperature property is assigned, using set_Temperature method calls:

 ob.set_Temperature(150);//Shouldnotraiseanevent ob.set_Temperature(99);//Shouldraiseanevent ob.set_Temperature(501);//Shouldraiseanevent 

The code can be compiled and linked with the OvenBean component from the command line:

 vjcComponentClient.jsl/reference:OvenBean.dll 

If you execute this program, you'll find that the C# component behaves in much the same way as the original JavaBean, and you'll get output that looks like that shown in Figure 3-4.

Figure 3-4. The output of the ComponentClient program

ComponentClient.jsl
 packageComponentConsumer; importOvenBean.*; publicclassComponentClient { publicstaticvoidmain(String[]args) { ComponentClientcc=newComponentClient(); } publicComponentClient() { //CreateanOvenBean OvenBeanob=newOvenBean(); //CreateadelegatethatreferstothehandleOvenEventmethod OvenBean.TemperatureEventHandlerhandler= newOvenBean.TemperatureEventHandler(handleOvenEvent); //SubscribetotheTemperatureEvent ob.AddOnTemperatureEvent(handler); //Testtheovenbysettingitstemperaturetodifferentvalues ob.set_Temperature(150);//Shouldnotraiseanevent ob.set_Temperature(99);//shouldraiseanevent ob.set_Temperature(501);//shouldraiseanevent } privatevoidhandleOvenEvent(System.Objectsender, TemperatureEventArgsargs) { System.out.println("TemperatureEventraised.Temperatureis " + args.get_Temperature()); } } 

Which component model should you use for your J# applications, JavaBeans or .NET? We recommend using the .NET functionality when you have a choice.

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net