Introduction (已看)
In The Beginning - Transaction Analysis (已看)
The Handlers Design Pattern (已看)
Frameworks (已看)
GUI programming (已看)
Maintaining State (已看)
Introduction
One effective way to explain a complex idea is to tell the story of its life.
In The Beginning - Transaction Analysis
Dataflow Diagrams
Structured analysis used dataflow diagrams (DFDs) to show the logical structure of a computer system.
On a DFD, a record in a sequential file was coneptualized as a packet of data moving through a pipeline, or along a conveyor belt, called a dataflow. Packets passed through a sequence of workstations called processes where they were filtered, used, enhanced, or transformed, and then passed on to the next workstation.
Describing a system in this way was called transform analysis.
De Macro also briefly described a second kind of analysis called transaction analysis and provided this diagram.
He explained the differences between transform and transaction analysis this way
- Transform analysis applies to applicatios that are transforms - that is, application that have clearly identified input streams, central processing, and output streams. A transform is represented in Data Flow Diagram terms by a linear network
- Transaction analysis applies to transaction centers, parts of the application characterized by sudden parallelism of data flow
"Transaction analysis", Yourdon and Constantine wrote, "is suggested by data flow graphs resembling Fig. 11.1 - that is, where a transform splits an input data stream into serveral discrete output sub-streams." Here is Figure 11.1, It is the archetype digrams of event-driven programming.
A transaction, they said, begins when "any element of data, control, signal, event, or change of state" is sent to the transaction center process.
A transaction center of a system must be able to
- get (obtain and respond to) transactions in a raw form
- analyze each transaction to determine its type
- dispatch on type of transaction
- complete the processing of each transaction
Structure Charts
Converting a transaction-processing dataflow diagram to a structure chart produced a structure diagram like this one(from Structured Design, p.205)
In this diagram, the dotted arrow coming in from the top represents flow of control being passed to the transaction center. Transactions are obtained by the GETTRAN function. Once obtained, a transaction is analyzed to determine its type (its transaction code) and then passed up to the transaction center. From there, it is passed to the DISPATCHER module which sends its to the module that handles transactions of that type
The Handlers Design Pattern
If Yourdon and Constantine were writing today, they might very well call their notation of transaction analysis a design pattern. I will call it Handlers pattern.
On the diagram you can see:
- a stream of data items called events (Yourdon and Constantine's "transactions")
- a dispatcher (Yourdon and Constantine's "transaction center")
- a set of handlers
The job of the dispatcher is to take each event that comes to it, analyze the event to determine it's event type and then send each event to a handler that can handle events of that type.
The dispatcher must process a stream of input events, so its logic must include an event loop so that it can get an event, dispatch it, and then loop back to obtain and process the next event in the input stream
Some application (for example, applications that control hardware) may treat the event stream as effectively infinite. But for most event-handling applications that event stream is finite, with an end indicated by some special event - an end-of-file marker, or a press of the ESCAPE key, or a left-click on a CLOSE button in a GUI. In those applications, the dispatcher logic must include a quit capability to break out of the event loop when the end-of-event-stream event is detected.
In some situations, the dispatcher may determine that it has no appropriate handler for the event. In those situation, it can either discard the event or raise (throw) an exception.GUI applications are typically interested in certain kinds of events (e.g. mouse button clicks) but uninterested in others (e.g. mouse movement events). So in GUI applications, event without handlers are typically discarded. For most other kinds of applications, an unrecognized event constitutes an error in the input stream and the appropriate action is to raise an exception.
Here is pseudo-code for a typical dispatcher that shows all of these features:
- the event loop
- the quit operation
- the determination of event type and the selection of an appropriate handler on the basis of that type, and
- the treatment of events without handlers
do forever: # the event loop get an event from the input stream if event type == EndOfEventStream: quit # break out of event loop if event type == ... : call the appropriate handler subroutine, passing it event information as an argument elif event type == ... : call the appropriate handler subroutine, passing it event information as an argument else: # handle an unrecognized type of event ignore the event, or raise an exception
The Headless Handlers Pattern
There are a few variants on the Handlers pattern. One of them is the Headless Handlers pattern, In this pattern, the dispatcher is either missing or not readily visible. Taking away the dispatcher, all that remains is a collection of event handlers.
The Extended Handlers Pattern
Another variant is the Extended Handlers pattern. In this variant, the pattern includes an events generator component that generates the stream of events that the dispacther processes.
The Event Queue
In some cases, the dispatcher and its handlers may not be able to handle events as quickly as they arrive. In such cases, the solution is to buffer the input stream of events by introducing an event queue into the event stream, between the events generator and the dispatcher Events are added to the end of the queue as fast as they arrive, and the dispatcher takes them off the front of the queue as fast as it is able.
GUI applications typically include an event queue. Significatn events such as mouse clicks may require some time to be handled. While that is happening, other events such as mouse-movement events may accumulate in the buffer. When the dispatcher becomes free again, it can rapidly discard the ignorable mouse-movement events and quickly empty the event queue.
Some Examples of the Handlers Pattern
These methods and technologies are probably familiar but you may never have thought of them as examples of event-driven programming.
Objects
In this object diagram, Stack is an object type (or class) Push, pop, and peek are its methods. To use the Stack class, you would create a stack object, and then use its methods to do things to it.
# create a stack object by instantiating the Stack class myStack = new Stack() myStack.push("abc") myStack.push("xyz") print myStack.pop() # prints "xyz" print myStack.peek() # prints "abc"
I like object diagrams because they clearly show objects as examples of the Headless Handlers pattern. An object diagram is basically the same as the Headless Handlers diagram, except that event arrive from the left rather than the top of the diagram. The Stack class, for example, is clearly a collection of event handlers (called methods in OO jargon) for handling push, pop, and peek events.
The lesson here is that if you're an object-oriented programmer you already know event-driven programming. When you write object methods, you are - quite literally - writing event handlers.
Systems
In Structured System Analysis, a computer system was conceptualized as a factory. Raw materials flow into the factory, travel along conveyor belts (data flow) through workstation (processes) and eventually a finished product is pushed out the door.
Here is a context diagram from p.59 of De Macro's Structured Analysis and System Specification
ESA reagards a computer system not as a factory, but as a stimulus/response machine. The stimuli are events sent into the system by the terminators in the outside world. The system itself is conceptualized as a collection of event handlers(essential activities). When an event arrives, the system springs to life, an essential activity processes the event, and then the system goes to sleep again (quiesces) until the arrival of the next event.
Essential activities respond to events by reading from and writing to data stores at the heart of the system, and by producing output data flows. The system's data stores constitue its essential memory
The diagram (from page 55 of Essential Systems Analysis) shows the essential parts of a computer system, as they were conceptualized in ESA
The design of a JSD system is shown in an SID (system implementation diagram). Here is a typical SID, from p. 293 of System Development
Client-Server Architecture
A familiar example of the Handler pattern occur in client-server architectures. A server is a piece of hardware or software that provides a service to clients that use the service. The server's job is to wait for service request from clients, to respond to service requests by providing the requested service, and then to wait for more requests. Examples of servers include: print servers, file servers, windows servers, database servers, application servers, and web servers.
Messaging System
Messaging systems represent an extreme version of the Handlers pattern. The purpose of a messaging system is to get events (messages) from event generators (senders) to handlers (receivers) in situations where the senders and receivers are in different physical locations or running on different platforms.
In messaging systems, messages are typically addressed to specific receivers, so the dispatching function (which determines which receiver should receive the message) is trivially simple. A familiar example of a messaging system is the post office. A sender gives a message (a letter or a parcel) to the post-office (the messaging system). The post office reads the receiver's address on the message and transports the message to the receiver
In addition to this point-to-point model of communications, MOM (message-oriented middleware) products also support a publish/subscribe model. In a publish/subscribe model, receivers become subscribers by subscribing to topics, and senders may send messages to topics (rather than to individual subscribers). When a topic receives a message, the topic forwards the message to all receivers who have subscribed to the topic.
Frameworks
Object-Oriented Event-Driven Programming
Before the advent of object-oriented programming, these event handlers would have been implemented as subroutines. The code inside the dispatcher's event loop might have looked like this:
get eventType from the input stream if eventType == EndOfEventStream : quit # break out of event loop if eventType == "A" : call addCustomerAccount() elif eventType == "U" : call setCustomerName() elif eventType == "C" : call closeCustomerAccount() ... and so on ...
And the subroutines might have looked something like this:
subroutine addCustomerAccount() : get customerName # from data-entry screen get next availabel accountName # generated by the system accountStatus = OPEN # insert a new row into the account table SQL: insert into account values (accountNumber, customerName, accountStatus) subroutine setCustomerName() : get accountNumber # from data-entry screen get customerName # from data-entry screen # update row in account table SQL: update account set customer_name = customerName where account_name = accountNumber subroutine closeCustomerAccount() : get accountNumber # from data-entry screen # update row in account table SQL: update account set status = CLOSED where account_num = accountNumber
Nowadays, using object-oriented technology, event handlers would be implemented as methods of objects. The code inside the dispatcher's event loop might look like this:
get eventType from the input stream if eventType == "end of event stream": quit # break out of event loop if eventType == "A" : get customerName # from data-entry screen get next available accountNumber # from the system # create an account object account = new Account(accountNumber, customerName) account.persist() elif eventType == "U" : get accountNumber # from data-entry screen get customerName # from data-entry screen # create an account object & load it from the database account = new Account(accountNumber) account.load() # update the customer name and persist the account account.setCustomerName(customerName) account.persist() elif eventType == "C" : get accountNumber # from data-entry screen # create an account object & load it from the database account = new Account(accountNumber) account.load() # close the account and persist it to the database account.close() account.persist() ... and so on ...
And the Account class, with its methods that function as event-handlers, would look something like this.
class Account: # the class's constructor method def initialize(argAccountNumber, argCustomerName) : self.accountNumber = argAccountNumber self.customerName = argCustomerName self.status = OPEN def setCustomerName(argCustomerName) : self.customerName = argCustomerName def close() : self.status = CLOSED def persist() : ... code to persit an account object def load() : .. code to retrieve an account object from persistent storage
Using OO technology this way isn't very exciting. Basically, we've just substitued objects for database records; otherwise, the processing is pretty much unchanged.
But it gets more interesting...
Frameworks
A plug-point is a place in the code where a software framework expects event-handlers to be "plugged in". The event-handlers themselves are called plug-ins (or, if you prefer, plugins)
The technical name for a half-baked class containing plug-points is an abstract class. Different programming languages provide different ways to define plug-points. Java, for example, provides the keyword "abstract" and plug-points are called abstract methods.
An abstract method isn't really a method. Rather, it is a place-holder for a method; a spot where a real(concrete) method can be plugged in.
The general term for a piece of software that work this way - that define plug-points and requires plug-ins - is framework. Here are some of the definitions you will get if you Google for the term "framework". Each of definitions captures part of the concept of a framework.
A framework is:
- a skeletal structure for supporting or enclosing something else
- a broad overview, outline or skeleton, within which details can be added
- an extensible software environment that can be tailored to the needs of a specific domain
- a collection of classes that provides a general solution to some application problem. A framework is usually refined to address the specific problem through specialization, or through additional types or classes
- a component that allow its functionality to be extended by writing plug-in modules("framework extensions"). The extension developer writes classes that derive from interfaces defined by the framework
- a body of software designed for high reuse, with specific plugpoints for the functionality required for a particular system. Once the plugpoints are supplied, the system will exhibit behavior that is centered around the plugpoints.
The pattern underlying the concept of a framework is the Handlers pattern. The "framework extensions" or "plug-ins" are event handlers.
SAX - an example of a framework
Frameworks come in all shapes and sizes, from very large to mini-framwork. To See how a real framework is used, let's look at one of the smaller ones: SAX(Actually, SAX is not a framework. it is an api that may be implemented in a framework. But to keep things simple, i'll talk as if it is a framework)
XML is surging in popularity. One consequence is that many programmers are encountering event-driven programming for the first time in the form of SAX - the Simple API for XML. SAX is an event-driven XML parser. Its job is to break (parse) XML into digestible pieces. For example, a SAX parser would parse the string: <superhero>Batman</superhero> into three pieces.
To use a SAX parser, you feed it a chunk of XML. It parses the XML text into different kinds of pieces and then invokes appropriate predefined plug-points(event-handlers) to handle the pieces.
SAX specifies predefined plug-points for a variety of XML features such as opening and closing tags (startElement, endElement), for the text between tags, comments, processing instructions, and so on
The SAX framework provides a Parser class and an abstract ContentHandler class. To use it, you first subclass the ContentHandler class and write concrete methods (event-handlers) to override the abstract methods. Here is a simple example in Python. All it does is print (i.e. write to the console) the XML tag names and the data between the tags.
# example Python code class CustomizedHanlder (ContentHandler) : def startElement(self, argTag, argAtrributess) : # write out the start tag, without any attributes print "<" + artTag + ">" def endElement(self, argTag) : print "<" + argTag + ">" def characters(self, argString) : print argString
Once you've extended the ContentHandler class and specified the event handlers, you:
- use the SAX make_parser factory function to create a parser object
- instantiate the CustomHandler class to create a myContentHandler object
- tell the parser object to use the myContentHandler object to handle the XML content
- feed the XML text to the parser and let the event handlers to all the work
Here is how it might be done in Python
myParser = xml.sax.make_parser() # create parser object myContentHandler = CustomizedHandler() # create content handler object # tell the parser object which ContentHandler object # to use the handle the XML content myParser.setContentHandler(myContentHandler) myInfile = open(myInfileName, "r") # open the input file myParser.parse(myInfile) # send it to the parser to be parsed myInfile.close() # close the input file
Why programming with a framework is hard
Note that the last step in the process consists only of feeding the XML text to the parser - nothing more. For a programmer with a background in procedural programming, this is waht makes event-driven programming confusing. In a procedural program, the main flow of control stays within the main application routine. Subordinate routines or modules are merely utilities or helpers invoked to perform lower-level tasks. The flow of control in the main routine is often long and complex, and its complexity gives the application a specific logical structure. The program has a shape, and that shape is visible to the programmer
But when a procedural programmer begins programming with a framework, he lose all of that. There is no discernable flow of control - the main routine doesn't do anything except start the framework's event-loop. And once the event-loop is started, it is the code hidden inside the framework that drives the action. What's left of the program seems to be little more than a collection of helper modules (event-handlers). In short, the program structure seems to be turned inside out.
So procedural programmers often find that, on first encounter, event-driven and framework-driven programming makes no sense at all! Experience and familiarity will gradually lessen that feeling, but there is no doubt about it - moving from procedural programming to event-driven programming requires a very real mental paradigm shift. This is the paradigm shift that Robin Dunn and Dafydd Rees were describing in the quotations at the beginning of this paper.
GUI programming
Why GUI programming is hard
GUI programming is hard; everybody admits it
First of all, a lot of work is required simply to specify the appearance of the GUI. Every widget - every button, label, menu, enter-box, list-box, etc. - must be told what it should look like (shape, size, foreground color, background color, border style, font, etc.), where it should position itself, how it should behave if the GUI is resized, and how it fits into the hierachical structure of the GUI as a whole. Just specifying the appearance of the GUI is a lot of work. (This is why there are IDEs and screen painters for many GUI frameworks. Their job is to ease the burdern of this part of GUI programming)
Second (and more relevant to the subject of this paper), GUI programming is hard because there are many, many kinds of events that must be handled in a GUI. Almost every widget on the GUI - every button, checkbox, radio button, data-entry filed, list box(including every item on the list), textbox(including horizontal and vertical sliders), menu bar, menu bar icon, drop-down menu, drop-down menu item, and so on - almost every one is an event generator capable of generating multiple kinds of events. As if that weren't enough, hardware input devices are also event generators. The mouse can generate left button clicks, right button clicks, left button double clicks, right button double clicks, button-down events(for initiating drag-and-drop operations), mouse motion events, button-up events(for concluding drag-and-drop operations), and others. Every letter key on the keyboard, every number key, punctuation key, and function key - both alone and in combination with the SHIFT, ALT, and CONTROL keys - can generate events. There a lot of kinds of events that can be fed into the dispatcher's event loop, and a GUI programmer has to write event handlers for every significant event that a GUI-user might generate.
Third, there is the fact that many GUI toolkits are supplied as frameworks. The purpose of GUI frameworks, like the SAX framework, it to ease the burden on the GUI programmer. GUI frameworks, for instance, supply the event-loop and the event queue, relieving the GUI programmer of that job. But, as we saw with SAX, using a framework means that large chunks of the control flow of the program are hidden inside the canned machinery of the framework and are invisible to the GUI programmer. This means that a GUI programmer must master the paradigm shift involved in moving to event-driven programming
It's a lot to deal with. And, as if that weren't enough, there is the Observer pattern to master...
The Observer Pattern
The longer version of the Hollywood Principle is:
"Don't call us. Give us your telephone number, and we will call you if and when we have a job for you "
And that, in essence, is the Observer pattern.
In the Observer pattern, there is one subject entity and multiple observer entities. (In the audition scenario, the directors is the subject and the actors are the observers.) The observers want the subject to notify them if events happen that might be of interest to them, so they register(leave instructions for how they can be reached) with the subject. The subject then notifies them when interesting events (such as casting calls) happen. To support this process, the subject keeps a list of the names and addresses of all of the observers that have registered with him. When an interesting event occurs, he goes through his list of observers, and notifies the observers who registered an interest in that kind of event.
The Observer pattern is a special case of the Handlers pattern, and for that reason I like to think of its as the Registered Handlers pattern.
# A short Python program showing the Observer pattern class TalentAgency: # define a TalentAgency class: the subject def __init__(self): # The talent agency constructor self.ourActors = [] # Initially our list of actor/subscribers is empty. def registerActor(self, argActor, argDesiredTypeOfRole): observer = Observer() # The agency creates a new observer object observer.actor = argActor observer.desiredRole = argDesiredTypeOfRole # add the observer to the agency's list of actor/subscribers self.ourActors.append(observer) def notify(self, castingCall) : # HotShots has been notified of a casting call self.notifyActors(castingCall) def notifyActors(self, castingCall): for observer in self.ourActors: # Look at each actor on our list of clients. if observer.desireRole == castingCall.role : # If the actor wants roles of this type print observer.actor, " got call for: ", castingCall.role class CastingCall: pass # define a CastingCall class class Observer: pass # define an Observer class # Now let's run a little demo... HotShots = TalentAgency() # We create a new talent agency called HotShots HotShots.registerActor("Larry", "male lead") # actors register with HotShots, indicating the kind of role that they are interested in HotShots.registerActor("Moe", "male lead") HotShots.registerActor("Curly", "comic sidekick") castingCall = CastingCall() # There is a casting Call -- castingCall.source = "Universal Studios" # Universal Studios is looking castingCall.role = "male lead" # for a male lead HotShots.notify(castingCall) # Hotshots gets notified of the casting call
Event Objects
There is one features of this program that you should pay special attention to. It is the use of CastingCall objects with attributes(instance variables) of source and role. Here, the CastingCall objects are event objects - objects for holding events.
Event objects are wonderful tools for doing event-driven programming. In pre-object-oriented programming languages, events or transactions were extremely limited. In many cases, if you sent an event to an event handler, all you were sending was a single string containing a transaction code. But object-oriented technology changed all that by allowing us to create and pass event objects. Event objects are essentially packets into which we can stuff as much information about an event as we might wish.
An event object will of course carry the name of the kind of event that triggered it. But, depending on the application, it might carry much more. In the case of the HotShots talent agency, the event objects contain information about the casting call's source and role
In other applications, we might want to put quite a lot of information into our event objects. Consider for isntance the famous Model-View-Controller (MVC) pattern. The Observern pattern is really the heart of MVC. In MVC, the Model is an object that manages the data and behavior of some application domain: it is the subject in the Observer pattern. Views register with the Model as observers. Whenever the Controller makes a change to the Model, the Model notifies its registered observers (the Views) that it (the Model) has changed.
The simplest version of MVC is called the pull version. In this version the event object (the change notification that the Model sends to the Views) contains hardly any information at all. The Model doesn't describe the change, it only notifies the Views that some kind of changes has taken place.When the Views receive such a notification, they must then pull information from the Model. That is, they must query the Model for information about its current state, and refresh themselves from that information
The more elaborate version of MVC is called the push version. In this version, the Model pushes out change information to the Views. The event object sent to the Views contains a great mass of information - a complete and detailed description of the change that has been made to the Model. When the Views receive this information, they have all the information they need in order to modify themselves, and they do
The basic difference between the push and pull versions of MVC is simply the amount of information that is stuffed into the event-object packet
The Registered Handlers pattern in GUI applications
# A short Python program showing the Registered Handlers pattern class Event: pass class Observer: pass # Create an event handler(i.e. an observer). In this case, the event handler is a function # It simply prints information about the event that it is handling def demoHandler(argEvent) print "Handling", argEvent.type, "from", argEvent.source class Dispatcher: # Define the subject def __init__(self): # __init__ = a Python constructor self.myObservers = [] # initialize a list of observers def registerObservers(self, argEventHandler, argEventType): # Register an observer. The argEventType argument indicates the event-type # that the observer/event-handler will handle observer = Observer() # Create an "observer" object observer.eventHandler = argEventHandler # and pack the event-handler observer.eventType = argEventType # and event-type into it self.myObservers.append(observer) # add observer to the "notify list" def eventArrival(self, argEvent) : self.notifyObservers(argEvent) # dispatch event to registered observers def notifyObservers(self, argEvent) : for observer in self.myObservers: # if the handler wants to handle events of this type... if observer.eventType == argEvent.type: # ...send the event to the handler observer.eventHandler(argEvent) # Run a demo. We simulate a GUI user doing a mouse-click. demoDispatcher = Dispatcher() # create a dispatcher object MOUSE_LEFT_DOUBLE = "LeftMouseDownClick" # define an event type # Register the event handler with the dispatcher. The second argument # specifies the type of event to be handle by the handler. demoDispatcher.registerObserver(demoHandler, MOUSE_LEFT_DOUBLE) demoEvent = Event() # Create an event object for demo purpose demoEvent.type = MOUSE_LEFT_DOUBLE # The event is a left double-click demoEvent.source = "mouse" # of the mouse demoDispatcher.eventArrival(demoEvent) # Send the mouse event to the dispatcher
Registering Event-Handlers in Python - "binding"
OkButton = Tkinter.Button(parent, text="OK") OkButton.bind("<Return>", OkButtonEventHandler) OkButton.bind("<Button-1>", OkButtonEventHandler)
Registering Event-Handlers in Java - "listeners"
For the event-handling aspects of the GUI, the java.awt.event package provides a number of different types of event-object
- ActionEvent
- AdjustmentEvent
- ComponentEvent
- ContainerEvent
- FocusEvent
- InputEvent
- InputMethodEvent
- InvocationEvent
- ItemEvent
- KeyEvent
- MouseEvent
- MouseWheelEvent
- PaintEvent
- TextEvent
Each of these event types contains variable and methods appropriate for events of that type. For example, MouseEvent objects contain variables and methods appropriate for mouse events, and KeyEvent objects contain variables and methods appropriate for keyboard events. For example:
- MouseEvent.getButton() reports which mouse button caused the event
- MouseEvent.getClickCount() reports the number of mouse clicks that triggered the event
- MouseEvent.getPoint()
- KeyEvent.getKeyChar() tells which key on the keyboard was pressed
The java.awt.event package also provides a generic EventListener interface and a collection of specialized listeners that extend it. Examples of the specialized listener interfaces are:
- ActionListener
- ContainerListener
- FocusListener
- InputMethodListener
- ItemListener
- KeyListener
- MouseListener
- MouseMotionListener
- MouseWheelListener
- TextListener
- WindowFocusListener
- WindowListener
These specialized listener interfaces are built around the different event types. That is, a listener interface is a collection of methods (event handlers), all of which handle the same type of event object. The methods in the MouseListener interface, for instance, handle MouseEvents; the methods in the KeyListener interface handle KeyEvents, and so on
Within the interfaces, the event handlers have descriptive, hard-coded names. For example, the MouseListener interface provides five event-handler methods:
- mouseClicked(MouseEvent e)
- mouseEntered(MouseEvent e)
- mouseExited(MouseEvent e)
- mousePressed(MouseEvent e)
- mouseReleased(MouseEvent e)
A GUI consists of multiple GUI components(widgets) such as panels, lists, buttons, etc. When a GUI program runs, the widgets are where the GUI events - mouse click, key presses, etc. - originate
When a GUI event - for example, a mouseClicked event - occurs on a subject widget, the widget will call the mouseClicked() event-handler method in the registered listener object and pass the mouseClicked evetn object to it
// We defien a class. This class is a mouse listener because // it implements the MouseListener interface // It also extends JPanel, so it is also a GUI widget object. public class DemoGUI extends JPanel implements MouseListener { public DemoGUI() { // the constructor for DemoGUI ... create inputArea and reportingArea widget/objects ... ... add them to our GUI ... // Tell the inputArea to add this object to its list // of mouse listeners, so when a mouse event occurs // in the inputArea widget, the event will be reported inputArea.addMouseListener(tshi); } public void mouseClicked(MouseEvent e) { // Over-ride the mouseClicked method. // This method will be called when a mosueClicked event occurs this.report("mouseClicked event (" + e.getClickCount() + " clicks)", e) // NOTE: second argument is the MosueEvent object, e } void report(String eventDescription, MouseEvent e) { // Display a message reporting the mouse event reportingArea.append(eventDescription + "detected on " + e.getComponent().getClass().getName() + ". "); } } // end class definition
In this code, the inputArea object is the event generator. The mouseClick() method is the event handler. It is also an example of the Registered Handlers/Observer pattern. inputArea is the subject in the Observer pattern, and the line:
inputArea.addMouseListener(this);
registers "this" (that is, DemoGUI) with inputArea as an observer.
Callback programming
When you read the documentation for GUI frameworks,you will notice that the observer/event-handlers may be referred to as callback because subject widgets "call back" to them to handle events. So you will often see this type of programming referred to as callback programming
GUI programming - summary
Maintaining State
Many event-driven applications are stateless. This means that when the application finishes processing an event, the application hasn't been changed by the event
The opposite of a stateless application is a stateful application. Stateful applications are applications that are chagned by the events that they process. Specifically, stateful applications remember or maitain information between events, and what they remember - their state information - can be changed by events.
A hammer is a stateless tool; if you drive a nail with it, the hammer is no different after you have driven the nail than it was before. A stapler is a stateful tool; if you staple some papers with it; the action of stapling changes its state - after the stapling action, the stapler contains one less staple than it did before
Rejecting invalid transactions
In most cases, object-oriented programming invovles objects that are stateful. An object such as a stack maitains its state in its instance variables - the area labeled "internal data" on this diagram
State Machines
In many cases, it is useful to think of an object - a stateful computer application - as having a lifecycle. During its lifecycle, the stateful object moves from state to state in response to transactions(events)
An object thatt behaves this way is called a Finite State Machine (FSM) or a Directed Finite Automaton (DFA). The typical way to describe a finite state machine is with a State Transition Diagram (STD). On an STD, states are represented as circles and transitions are represented by arrows labeled with the name of the event that causes the transition
class Person: def __init__(): self.status = "SINGLE"; self.marriageCounter = 0 def getMarried(): if self.status == "SINGLE": self.status = "MARRIED" self.marriageCounter = self.marriageCounter + 1 else: raise InvalidTransaction(self.status, "getMarried") def getDivorced(): if self.status == "MARRIED": self.status = "SINGLE" else: raise InvalidTransaction(self.status, "getDivorced") def spouseDies(): if self.status == "MARRIED": self.status = "SINGLE else: raise InvalidTransaction(self.status, "spouseDies") def die(): if self.status == "DEAD": raise InvalidTransaction(self.status, "die") else; self.status = "DEAD"
Coding a Finite State Machine(1)
First of all, it is a parsing program. That is, it reads an input stream of characters, breaks it up into groups of characters (tokens), and labels the token by type. Parsing - which is done in the tokenizing (lexical analysis) phase of compilers, and in processing makrup languages such as HTML and XML - is the classic application for illustrating the implementating of a finite state machine
Second, it uses a classic desgin pattern for coding finite state machines. For many applications, a FSM program must do three things when processing a transaction:
- activities assoiated with leaving the current state
- change the current status to the new status
- activities associated with entering the new state
Finally, the program shows the features that we expect to see in the Handlers pattern. The input stream is the event queue. Reading a character from the input stream is the plucking of an event off of the event queue. An event loop examines each character/event/transaction to determine whether it is a letter or space, and dispatches it to the appropriate handler. And of course there is code to break out of the event loop when an EndOfEvents(end of input stream) event is detected.
#------------------------------------------- # some infrastructure #------------------------------------------ # Python-specific code to get a character from the eventStream def GetChar(): for character in eventSystem: yiled character yiled None # ... when there are no more characters getchar = GetChar() START = "start...:" # constants for state names SPACES = "spaces.:" LETTERS = "letters:" END = "end...:" def quote(argString): return '"' + argString + '"' #------------------------------------------- # the event-handlers #----------------------------------------------- def handleSpace(c): global state, outstring if state == START: # activities for exiting the current state # -- nothing to do when leaving START state # change the status to the new state state = SPACES # activities for entering the new state outstring = c elif state == SPACES: # activities for exiting the current state # -- do nothing: new state is same as old state # change the status to the new state # -- do nothing: new state is same as old state # activities for entering the new state outstring = outstring + c elif state == LETTERS # activities for exiting the current state print state, quote(outstring) # change the status to the new state state = SPACES # activities for entering the new state outstring = c def handleLetter(c): global state, outstring if state == START: # activities for exiting the current state # -- nothing to do when leaving START state # change the status to the new state state = LETTERS # activities for entering the new state outstring = c elif state == LETTERS: # activities for exiting the current state # -- do nothing: new state is same as old state # change the status to the new state # -- do nothing: new state is same as old state # activities for entering the new state outstring = outstring + c elif state == SPACES: # activities for exiting the current state print state, quote(outstring) # change the status to the new state state = LETTERS # activities for entering the new state outstring = c def handleEndOfInput(): global state, outstring if state == START: raise Exception("ERROR: Input stream was empty!") else: # activities for exiting the current state print state, quote(outstring) # change the status to the new state state = END # activities for entering the new state # -- nothing to do to startup END state #------------------------------- # the driver routine, the event loop #------------------------------- # Create an eventSystem so we can demo the application eventSystem = "Suzy Smith loves John Jones" state = START # initialize the state-machine in the START state while True: # do forever: this is the event loop c = getchar.next() # get the character (event) if c == None: # indicates end of the event stream handleEndOfInput() break # break out of the event loop elif c == " ": handleSpace(c) else: # a "letter" is any non-space character handleLetter(c)
"Suzy Smith loves John Jones"
Coding a Finite State Machine(2)
Ways to remeber state
Conclusion