By Jonathan Simon
original:
http://today.java.net/pub/a/today/2003/10/24/swing.html?page=2
Solution: Event Drive Programming
All of these solutions have a common fatal defect - attempt to represent a task's function set while continuously changing threads. However, changing the thread requires an asynchronous model, while the thread processes Runnable asynchronously. Part of the problem is that we have a synchronized model at an attempt to achieve a synchronous model above an asynchronous thread model. This is the root of all runnails and dependence, execution order and internal SCoPing issues. If we can build a real asynchronous, we can solve our problems and greatly simplify the Swing thread.
Before this, let's listen to the questions we have to solve:
1. Execute the code in the appropriate thread
2. Use swingutilities.invokelater () asynchronously.
Asynchronous implementation leads to the following questions:
1. Mutual coupled components
2. Difficulties in variable transmission
3. The order of execution
Let us consider messaging based systems like Java Messaging (JMS) because they provide loosely coupled coupling between functional components in asynchronous environments. Message system triggers asynchronous events, just as
Enterprise Integration Patterns described. Interested participants listen to the event and respond to the event - usually by executing some of their own code. The result is a group of modular, loosely coupled components, and components can be added or removed from the system without affecting other components. More importantly, the dependence between components is minimized, and each component is a good definition and package - each of them is only responsible for their work. They simply trigger the message, and other components will respond to this message and respond to messages triggered by other components.
Now, we first ignore the thread problem and decouple the components and transplanted into the asynchronous environment. After we solve the asynchronous problem, we will go back to see thread problems. As we have to see, it will be very easy to solve this problem.
Let us take the examples introduced in front and transplant it to an event-based model. First, we call the lookup to a class called lookupManager. This will allow us to remove database logic in all UI classes and ultimately allow us to completely disengage the two. Below is the code of the LookupManager class:
Class lookupmanager {
Private string [] lookup (string text) {
String [] results = ...
// Database Lookup Code
Return RESULTS
}
}
Now let's start to convert to the asynchronous model. In order to call this call asynchronously, we need to return to the abstract call. In other words, the method cannot return any value. We will have something you want to know in terms of distinctive action. The most obvious event in our example is the search end event. So let's create a listener interface to respond to these events. This interface contains a single method lookupCompleted (). Below is the definition of the interface:
Interface lookuplistener {
public
Void LookUpCompleted (Iterator Results);
}
Comply with Java standards, we create another class called Lookupevent contains the result string array, not directly to the string array. This will allow us to pass other information without changing the LookUplistener interface. For example, we can include the strings and results that are found in the Lookupevent. Below is the Lookupevent class:
public
Class lookupevent {
String searchText;
String [] Results;
Public Lookupevent (String searchText) {
THIS.SEARCHTEXT = SearchText;
}
Public Lookupevent (String SearchText,
String [] results) {
THIS.SEARCHTEXT = SearchText;
THIS.RESULTS = Results;
}
Public string getsearchtext () {
Return SearchText;
}
Public string [] getResults () {
Return Results;
}
}
Note that the Lookupevent class is not variable. This is very important because we don't know who will deal with these events during the pass. Unless we created an event's protection copy to each listener, we need to make events unstrenomed. If not, a listener may unintentionally or maliciously revisit the event object and destroy the system.
Now we need to call the LookUpComplete () event on lookupManager. Let's first add a collection of lookuplistener on the lookupManager:
List listener = new arraylist ();
And provide methods of adding and removing LookUplistener on lookupManager:
public
Void AddLookupListener (Lookuplistener Listener) {
Listeners.Add (Listener);
}
public
Void RemovelookUplistener (Lookuplistener Listener) {
Listeners.Remove (Listener);
}
When the action occurs, we need to call the listener's code. In our example, we will trigger a lookupCompleted () event while looking back. This means it iterations on the listener collection and uses a lookupCompleted () method using a lookupevent event object.
I like to extract these code to a separate method Fire [event-method-name], which constructs an event object, iterations on the listener collection, and calls the appropriate method on each listener. This helps isolating the code of the primary logic code and calling the listener. Here is our FirelookupCompleted method:
Private
Void FirelookuppCompleted (String SearchText,
String [] results) {
LOOKUPEVENT EVENT =
New lookupevent (searchText, results);
Iterator it =
New arraylist (listener) .Itemrator ();
While (ore.hasnext ()) {
LookUplistener Listener =
(Lookuplistener) ore.next ();
Listener.lookUpCompleted (Event);
}
}
The second line code created a new collection that passed into the original listener collection. This decides on the LookupManager after the listener response event. If we are not a secure copy collection, a bored error occurred while some listeners should be called without being called.
Below, we will call the FirelookUpCompleted auxiliary method when the action is completed. This is the end of the LOOKUP method's return query results. So we can change the Lookup method to trigger an event rather than return a string array itself. Here is a new lookup method: public
Void lookup (string text) {
// mimic the server call delay ...
Try {
Thread.sleep (5000);
}
Catch (Exception E) {
E.PrintStackTrace ();
}
// Imagine We got this from a server
String [] results =
NEW STRING [] {] {
"Book One",
"BOOK TWO",
"Box three"}
FirelookUpCompleted (Text, Results);
}
Let us add the listener to lookupmanager. We want to update the text area when you look back. In the past, we just call the setText () method directly. Because the text area is executed in the UI together with the database call. Since we have abstracted from the UI, we will use the UI class as a listener to the LookusManager, listen to the Lookup event and update yourself accordingly. First we will implement the listener interface in the class definition:
Public Class fixedframe imports lookuplistener
Then we implement the interface method:
public
Void LookuppCompleted (
Final Lookupevent E) {
Outputta.settext
"" "
String [] results = E.GETRESULTS ();
FOR
INT i = 0; i String result = results [i]; Outputta.Settext (Outputta.getText () "/ n" result); } } Finally, we registered it as a listener of lookupManager: Public fixedframe () { LookupManager = New lookupManager (); // Here We Register the Listener LookupManager.Addlistener THIS); INITComponents (); LayoutComponents (); } For simplicity, I added it to the listener in the class constructor. This is allowed to be good on most systems. When the system becomes more complicated, you may be reconstructed, extracting the monitoring code from the constructor to allow greater flexibility and scalability. So far, you see the connection between all components, pay attention to the separation of responsibilities. The user interface class is responsible for information display - and only responsible for information display. On the other hand, the LookUpManager class is responsible for all Lookup connection and logic. Also, lookupManager is responsible for notifying the listener when it changes - instead of what should be specifically do it when the change occurs. This allows you to connect to the listener. In order to demonstrate how to add new events, let's add an event starting with a lookup. We can add an event called lookupstarted () to lookuplistener, and we will trigger it before the lookup starts execution. We also create a FirelookupStarted () event call all lookuplistener's lookstarted (). The LOOKUP method is now as follows: public Void lookup (string text) { Firelookupstarted (Text); // mimic the server call delay ... Try { Thread.sleep (5000); } Catch (Exception E) { E.PrintStackTrace (); } // Imagine We got this from a server String [] results = NEW STRING [] {] { "Book One", "BOOK TWO", "Box three"} FirelookUpCompleted (Text, Results); } We also add new triggers FirelookupStarted (). This method is equivalent to the FirelookUpCompleted () method, except that we call the lookupstarted () method on the listener, and the event does not contain result sets. Below is the code: Private Void Firelookupstarted (String searchText) { LOOKUPEVENT EVENT = New lookupevent (searchText); Iterator it = New arraylist (listener) .Itemrator (); While (ore.hasnext ()) { LookUplistener Listener = (Lookuplistener) ore.next (); Listener.lookupstarted (Event); } } Finally, we implements a lookstarted () method on the UI class, setting the text area prompt the current search string. public Void Lookupstarted Final Lookupevent E) { Outputta.settext "Searching for:" E.GetSearchText ()); } This example shows how easy it is to add new events. Now let's take a look at the flexibility of displaying event-driven decoupling. We will demonstrate by creating a log class while outputting information in the command line when a search start and end. We call this class as Logger. Here is its code: public Class Logger IMPLEMENTS LOOKUPLISTENER { public Void LookupStarted (Lookupevent E) { System.out.println ( "Lookup Started:" E.GetSearchText ()); } public Void LookUpCompleted (Lookupevent E) { System.out.println ( "Lookup completed:" E.GetSearchText () " E.GETRESULTS ()); } } Now, we add Logger as a listener for the LookUpManager in the FixedFrame constructor. Public fixedframe () { LookupManager = New lookupManager (); lookupmanager.addlistener THIS); LookupManager.Addlistener NEW logger ()); INITComponents (); LayoutComponents (); } Now you have seen add new events, create new listeners - show you the flexibility and scaife of the event driver. You will find that as you have more developed a program, you will be more skilled in your application to create a universal action. Like all other things, this requires only time and experience. It seems that many studies have been made on the event model, but you still need to compare it with other alternatives. Consider development time costs; the most important, this is a one-time cost. Once you have created a listener model and their actions, add a listener to your app in your app. Thread By now, we have solved the above asynchronous problems; through the listener to deliver the components, pass the variables through the event object, the order of execution through the event generation and the synonym registration. Let us return to thread problems, because it is brought here. It is actually very easy: Because we already have an asynchronous listener, we can simply let the listener decide which thread should be executed in. Consider the separation of the UI class and the LookUpManager. The UI class is based on an event and decides what processing needs. And, this class is also swing, and the log class is not. So let the UI class be responsible for determining what it should perform in what thread will make more meaningful. So let's take a look at the UI class again. Below is a LOOKUPCOMPLETED () method without a thread: public Void LookuppCompleted ( Final Lookupevent E) { Outputta.settext "" " String [] results = E.GETRESULTS (); FOR INT i = 0; i String result = results [i]; Outputta.Settext (Outputta.getText () "/ n" result); } } We know that this will be called in a non-Swing thread because the event is triggered in the lookupManager, which will not be executed in the Swing thread. Because all code functions are asynchronous (we don't have to wait for the listener method to allow you to call other code), we can connect these code to Swing threads via Swingutilities.Invokelater (). Below is a new method, incoming an anonymous runnable to swingutilities.invokelater (): public Void LookuppCompleted ( Final Lookupevent E) { // Notice the Thieading Swingutilities.invokelater ( New runnable () { public Void Run () { Outputta.settext "" " String [] results = E.GETRESULTS (); FOR INT i = 0; I i ) { String result = results [i]; Outputta.Settext (Outputta.getText () "/ n" result); } } } ); } If any lookuplistener is not executed in the Swing thread, we can perform the listener code in the calling thread. As a principle, we hope that all listeners will quickly receive notifications. So, if you have a listener that takes a lot of time to handle your own feature, you should create a new thread or put the time-consuming code in ThreadPool. The final step is to let lookupManager perform Lookup in a non-Swing thread. Currently, lookupManager is called in the SWING thread of JButton's ActionListener. Now we make a decision, or we introduce a new thread in JButton's ActionListener, or we can ensure that lookup is executed in a non-Swing thread, starting a new thread. I choose to manage Swing threads near the Swing class. This helps to package all Swing logic. If we add Swing thread logic to lookupManager, we will introduce a layer of unnecessary dependencies. Also, for the WOKUPMANAGER incubation to incubation in a non-Swing thread environment is completely unnecessary, such as a non-drawing user interface, in our example, is Logger. An unnecessary new thread will damage the performance of your application, not to improve performance. LookupManager is performing very well, no matter whether Swing thread is - so I like to concentrate the code there. Now we need to perform the JButton's ActionListener to play the LOOKUP code in a non-Swing thread. We created an anonymous Thread, using an anonymous runnable to execute this lookup. Private Void searchButton_ActionPerformed () { New thread () { public Void Run () { LookupManager.lookup (SearchTf.getText ()); } } .start (); } This completes our SWING thread. Simply add a thread to the actionPerformed () method, make sure the listener performs the entire thread problem in the new thread. Note that we don't have to handle any problems like the first example. By spending time in defining an event-driven system, we save more time on the switching thread related processing. in conclusion If you need to perform a lot of Swing code and non-Swing code in the same method, you can easily place some code to make the wrong position. The way the event drive will force you to place the code in what it should be there - it should only be there. If you execute a database call and update the UI component in the same method, you will write too much logic in a class. Analyze the events in your system, create the underlying event model will force you to put the code in the right place. Calling the time-tired database is placed in a non-UI class, or you do not update the UI component in the non-UI component. Using an event-driven system, the UI is responsible for the UI update, the database management class is responsible for the database call. At this point, each package is used only to care about your thread, don't worry about how the system is movable. Of course, the design, building an event-driven client is also useful, but it takes time to expect the flexibility and maintainability of the result system.